From 8ea2dc2202280b8be9829e6d0d25ccf6e78b8aca Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Wed, 13 Nov 2013 19:21:03 +0100 Subject: [PATCH 001/226] Remove tastypie dependency --- codespeed/tests/tests_api.py | 1434 ---------------------------------- codespeed/urls.py | 18 - sample_project/settings.py | 2 - sample_project/urls.py | 10 - 4 files changed, 1464 deletions(-) delete mode 100644 codespeed/tests/tests_api.py diff --git a/codespeed/tests/tests_api.py b/codespeed/tests/tests_api.py deleted file mode 100644 index 124c9de6..00000000 --- a/codespeed/tests/tests_api.py +++ /dev/null @@ -1,1434 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Tests related to RESTful API -""" -from datetime import datetime -import copy -import json -import logging -import unittest - -from django import test -from django.db.utils import IntegrityError -from django.test.client import Client -from django.http import HttpRequest -from django.core.urlresolvers import reverse -from django.conf import settings -from django.contrib.auth.models import User, Permission -from tastypie.authorization import (Authorization, ReadOnlyAuthorization, - DjangoAuthorization) -from tastypie.exceptions import ImmediateHttpResponse, Unauthorized -from tastypie.models import ApiKey, create_api_key -from tastypie.http import HttpUnauthorized -from tastypie.authentication import ApiKeyAuthentication - -from codespeed.api import EnvironmentResource, ProjectResource -from codespeed.models import (Project, Benchmark, Revision, Branch, - Executable, Environment, Result, Report) -from codespeed.api import ResultBundle -from codespeed import settings as default_settings - - -class FixtureTestCase(test.TestCase): - fixtures = ["gettimeline_unittest.json"] - - def setUp(self): - self.api_user = User.objects.create_user( - username='apiuser', email='api@foo.bar', password='password') - self.api_user.save() - self.api_user.user_permissions.clear() - john_doe = User.objects.create_user( - username='johndoe', email='api@foo.bar', password='password') - john_doe.save() - authorization = 'ApiKey {0}:{1}'.format(self.api_user.username, - self.api_user.api_key.key) - self.post_auth = {'HTTP_AUTHORIZATION': authorization} - - def generic_test_authorized(self, method, request, function_list, function_detail): - bundle = self.resource.build_bundle(request=request) - bundle.request.method = method - self.assertTrue( - function_detail(self.resource.get_object_list(bundle.request)[0], - bundle)) - - def generic_test_unauthorized(self, method, request, function_list, function_detail): - bundle = self.resource.build_bundle(request=request) - bundle.request.method = method - self.assertRaises(Unauthorized, function_detail, - self.resource.get_object_list(bundle.request)[0], bundle) - - -class ApiKeyAuthenticationTestCase(FixtureTestCase): - - def setUp(self): - super(ApiKeyAuthenticationTestCase, self).setUp() - ApiKey.objects.all().delete() - self.auth = ApiKeyAuthentication() - self.request = HttpRequest() - - # Simulate sending the signal. - user = User.objects.get(username='apiuser') - create_api_key(User, instance=user, created=True) - - def test_is_not_authenticated(self): - """Should return HttpUnauthorized when incorrect credentials are given""" - # No username/api_key details - self.assertEqual(isinstance( - self.auth.is_authenticated(self.request), HttpUnauthorized), True) - - # Wrong username details. - self.request.GET['username'] = 'foo' - self.assertEqual(isinstance( - self.auth.is_authenticated(self.request), HttpUnauthorized), True) - - # No api_key. - self.request.GET['username'] = 'daniel' - self.assertEqual(isinstance( - self.auth.is_authenticated(self.request), HttpUnauthorized), True) - - # Wrong user/api_key. - self.request.GET['username'] = 'daniel' - self.request.GET['api_key'] = 'foo' - self.assertEqual(isinstance( - self.auth.is_authenticated(self.request), HttpUnauthorized), True) - - def test_is_authenticated(self): - """Should correctly authenticate when using an existing user and key""" - # Correct user/api_key. - user = User.objects.get(username='apiuser') - self.request.GET['username'] = 'apiuser' - self.request.GET['api_key'] = user.api_key.key - self.assertEqual(self.auth.is_authenticated(self.request), True) - - -class UserTest(FixtureTestCase): - - """Test api user related stuff""" - - def test_has_apikey(self): - """User() should have an api key attr that was generated automatically.""" - self.assertTrue(hasattr(self.api_user, 'api_key')) - - def test_len_apikey(self): - """Should have api user key with a non-zero length.""" - self.assertTrue(len(self.api_user.api_key.key) >= 1) - - def test_is_authenticated_header(self): - """Taken from tastypie test suite to ensure api key is generated for - new users correctly and tastypie is installed correctly. - """ - auth = ApiKeyAuthentication() - request = HttpRequest() - - # Simulate sending the signal. - john_doe = User.objects.get(username='johndoe') - - # No username/api_key details should fail. - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong username details. - request.META['HTTP_AUTHORIZATION'] = 'foo' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # No api_key. - request.META['HTTP_AUTHORIZATION'] = 'ApiKey daniel' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Wrong user/api_key. - request.META['HTTP_AUTHORIZATION'] = 'ApiKey daniel:pass' - self.assertEqual(isinstance(auth.is_authenticated(request), HttpUnauthorized), True) - - # Correct user/api_key. - john_doe = User.objects.get(username='johndoe') - request.META['HTTP_AUTHORIZATION'] = 'ApiKey johndoe:%s' % john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - - # Capitalization shouldn't matter. - john_doe = User.objects.get(username='johndoe') - request.META['HTTP_AUTHORIZATION'] = 'aPiKeY johndoe:%s' % john_doe.api_key.key - self.assertEqual(auth.is_authenticated(request), True) - - def test_api_key(self): - """User should be authenticated by it's api key.""" - auth = ApiKeyAuthentication() - request = HttpRequest() - authorization = 'ApiKey %s:%s' % (self.api_user.username, - self.api_user.api_key.key) - request.META['HTTP_AUTHORIZATION'] = authorization - self.assertEqual(auth.is_authenticated(request), True) - - -class EnvironmentDjangoAuthorizationTestCase(FixtureTestCase): - - """Test Environment() API""" - - def setUp(self): - super(EnvironmentDjangoAuthorizationTestCase, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_environment', 'codespeed', 'environment') - self.change = Permission.objects.get_by_natural_key( - 'change_environment', 'codespeed', 'environment') - self.delete = Permission.objects.get_by_natural_key( - 'delete_environment', 'codespeed', 'environment') - self.env1_data = dict( - name="env1", - cpu="cpu1", - memory="48kB", - os="ZX Spectrum OS", - kernel="2.6.32" - ) - self.env1 = Environment(**self.env1_data) - self.env1.save() - self.env2_data = dict( - name="env2", - cpu="z80", - memory="64kB", - os="ZX Spectrum OS", - kernel="2.6.32" - ) - env_db1 = Environment.objects.get(id=1) - self.env_db1_data = dict( - [(k, getattr(env_db1, k)) for k in self.env1_data.keys()] - ) - self.client = Client() - self.resource = EnvironmentResource() - self.auth = self.resource._meta.authorization - - def test_no_perms(self): - """User() should have only GET permission""" - # sanity check: user has no permissions - self.assertFalse(self.api_user.get_all_permissions()) - - request = HttpRequest() - request.method = 'GET' - request.user = self.api_user - - # with no permissions, api is read-only - self.generic_test_authorized('GET', request, self.auth.read_list, - self.auth.read_detail) - - methods = [('POST', self.auth.create_list, self.auth.create_detail), - ('PUT', self.auth.update_list, self.auth.update_detail), - ('DELETE', self.auth.delete_list, self.auth.delete_detail)] - - for method, function_list, function_detail in methods: - self.generic_test_unauthorized(method, request, function_list, - function_detail) - - def test_add_perm(self): - """User() should have add permission granted.""" - request = HttpRequest() - request.user = self.api_user - - # give add permission - request.user.user_permissions.add(self.add) - - self.generic_test_authorized('POST', request, self.auth.create_list, - self.auth.create_detail) - - def test_change_perm(self): - """User() should have change permission granted.""" - request = HttpRequest() - request.user = self.api_user - - # give change permission - request.user.user_permissions.add(self.change) - - self.generic_test_authorized('PUT', request, self.auth.update_list, - self.auth.update_detail) - - def test_delete_perm(self): - """User() should have delete permission granted.""" - request = HttpRequest() - request.user = self.api_user - - # give delete permission - request.user.user_permissions.add(self.delete) - - self.generic_test_authorized('DELETE', request, self.auth.delete_list, - self.auth.delete_detail) - - def test_all(self): - """User() should have add, change, delete permissions granted.""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - methods = [('GET', self.auth.read_list, self.auth.read_detail), - ('OPTIONS', self.auth.read_list, self.auth.read_detail), - ('HEAD', self.auth.read_list, self.auth.read_detail), - ('POST', self.auth.create_list, self.auth.create_detail), - ('PUT', self.auth.update_list, self.auth.update_detail), - ('PATCH', self.auth.read_list, self.auth.read_detail), - ('PATCH', self.auth.update_list, self.auth.update_detail), - ('PATCH', self.auth.delete_list, self.auth.delete_detail), - ('DELETE', self.auth.delete_list, self.auth.delete_detail)] - - for method, function_list, function_detail in methods: - self.generic_test_authorized(method, request, function_list, - function_detail) - - def test_patch_perms(self): - """User() should have patch (add, change, delete) permissions granted.""" - request = HttpRequest() - request.user = self.api_user - request.method = 'PATCH' - - # Not enough. - request.user.user_permissions.add(self.add) - self.generic_test_unauthorized('PATCH', request, self.auth.update_list, - self.auth.update_detail) - self.generic_test_unauthorized('PATCH', request, self.auth.delete_list, - self.auth.delete_detail) - - # Still not enough. - request.user.user_permissions.add(self.change) - self.generic_test_unauthorized('PATCH', request, self.auth.delete_list, - self.auth.delete_detail) - - # Much better. - request.user.user_permissions.add(self.delete) - # Nuke the perm cache. :/ - del request.user._perm_cache - self.generic_test_authorized('PATCH', request, self.auth.delete_list, - self.auth.delete_detail) - - def test_unrecognized_method(self): - """User() should not have the permission to call non-existent method.""" - request = HttpRequest() - self.api_user.user_permissions.clear() - request.user = self.api_user - - # Check a non-existent HTTP method. - request.method = 'EXPLODE' - self.generic_test_unauthorized( - 'EXPLODE', request, self.auth.update_list, - self.auth.update_detail) - - def test_get_environment(self): - """Should get an environment when given an existing ID""" - response = self.client.get( - '/api/v1/environment/1/?username={0}&api_key={1}'.format( - self.api_user.username, self.api_user.api_key.key)) - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], "Dual Core") - - def test_get_non_existing_environment(self): - """Should return 404 when given a non existing environment ID""" - response = self.client.get( - '/api/v1/environment/999/?username={0}&api_key={1}'.format( - self.api_user.username, self.api_user.api_key.key)) - self.assertEquals(response.status_code, 404) - - def test_get_environment_all_fields(self): - """Should get all fields for an environment""" - response = self.client.get( - '/api/v1/environment/{0}/?username={1}&api_key={2}'.format( - self.env1.pk, self.api_user.username, self.api_user.api_key.key) - ) - self.assertEquals(response.status_code, 200) - for k in self.env1_data.keys(): - self.assertEqual( - json.loads(response.content)[k], getattr(self.env1, k)) - - def test_post(self): - """Should save a new environment""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - response = self.client.post('/api/v1/environment/', - data=json.dumps(self.env2_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/environment/', - data=json.dumps(self.env2_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - #response = self.client.get('/api/v1/environment/{0}/'.format(id)) - response = self.client.get( - '/api/v1/environment/{0}/?username={1}&api_key={2}'.format( - id, self.api_user.username, self.api_user.api_key.key) - ) - for k, v in self.env2_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - response = self.client.delete('/api/v1/environment/{0}/'.format(id), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - def test_put(self): - """Should modify an existing environment""" - modified_data = copy.deepcopy(self.env_db1_data) - modified_data['name'] = "env2.2" - modified_data['memory'] = "128kB" - response = self.client.put('/api/v1/environment/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 401) - - request = HttpRequest() - request.user = self.api_user - request.user.user_permissions.add(self.change) - - response = self.client.put('/api/v1/environment/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/environment/1/') - response = self.client.get( - '/api/v1/environment/1/?username={0}&api_key={1}'.format( - self.api_user.username, self.api_user.api_key.key) - ) - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete an environment""" - response = self.client.get( - '/api/v1/environment/1/?username={0}&api_key={1}'.format( - self.api_user.username, self.api_user.api_key.key)) - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/environment/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 401) - - request = HttpRequest() - request.user = self.api_user - request.user.user_permissions.add(self.delete) - response = self.client.delete('/api/v1/environment/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - response = self.client.get( - '/api/v1/environment/1/?username={0}&api_key={1}'.format( - self.api_user.username, self.api_user.api_key.key)) - self.assertEquals(response.status_code, 404) - - # from just created data - response = self.client.get( - '/api/v1/environment/{0}/?username={1}&api_key={2}'.format( - self.env1.pk, self.api_user.username, self.api_user.api_key.key) - ) - self.assertEquals(response.status_code, 200) - response = self.client.delete( - '/api/v1/environment/{0}/'.format(self.env1.id), - content_type='application/json', - **self.post_auth - ) - self.assertEquals(response.status_code, 204) - - response = self.client.get( - '/api/v1/environment/{0}/?username={1}&api_key={2}'.format( - self.env1.pk, self.api_user.username, self.api_user.api_key.key) - ) - self.assertEquals(response.status_code, 404) - - -class ProjectTest(FixtureTestCase): - - """Test Project() API""" - - def setUp(self): - super(ProjectTest, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_project', 'codespeed', 'project') - self.change = Permission.objects.get_by_natural_key( - 'change_project', 'codespeed', 'project') - self.delete = Permission.objects.get_by_natural_key( - 'delete_project', 'codespeed', 'project') - self.project_data = dict( - name="PyPy", - repo_type="M", - repo_path="ssh://hg@bitbucket.org/pypy/pypy", - repo_user="fridolin", - repo_pass="secret", - ) - self.project_data2 = dict( - name="project alpha", - repo_type="M", - repo_path="ssh://hg@bitbucket.org/pypy/pypy", - repo_user="alpha", - repo_pass="beta", - ) - self.project = Project(**self.project_data) - self.project.save() - self.client = Client() - self.resource = ProjectResource() - self.auth = self.resource._meta.authorization - - def test_all(self): - """User should have all permissions granted.""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - methods = [('GET', self.auth.read_list, self.auth.read_detail), - ('OPTIONS', self.auth.read_list, self.auth.read_detail), - ('HEAD', self.auth.read_list, self.auth.read_detail), - ('POST', self.auth.create_list, self.auth.create_detail), - ('PUT', self.auth.update_list, self.auth.update_detail), - ('PATCH', self.auth.read_list, self.auth.read_detail), - ('PATCH', self.auth.update_list, self.auth.update_detail), - ('PATCH', self.auth.delete_list, self.auth.delete_detail), - ('DELETE', self.auth.delete_list, self.auth.delete_detail)] - - for method, function_list, function_detail in methods: - self.generic_test_authorized(method, request, function_list, - function_detail) - - def test_get_project(self): - """Should get an existing project""" - response = self.client.get('/api/v1/project/{0}/'.format( - self.project.id,)) - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], "{0}".format( - self.project_data['name'])) - - def test_get_project_all_fields(self): - """Should get all fields for a project""" - response = self.client.get('/api/v1/project/%s/' % (self.project.id,)) - self.assertEquals(response.status_code, 200) - for k in self.project_data.keys(): - self.assertEqual( - json.loads(response.content)[k], getattr(self.project, k)) - - def test_post(self): - """Should save a new project""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - response = self.client.post('/api/v1/project/', - data=json.dumps(self.project_data2), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/project/', - data=json.dumps(self.project_data2), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - response = self.client.get( - '/api/v1/project/{0}/?username={1}&api_key={2}'.format( - self.project.id, self.api_user.username, self.api_user.api_key.key) - ) - for k, v in self.project_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete an project""" - response = self.client.delete('/api/v1/project/{0}/'.format( - self.project.id,), content_type='application/json') - self.assertEquals(response.status_code, 401) - - request = HttpRequest() - request.user = self.api_user - request.user.user_permissions.add(self.delete) - response = self.client.delete( - '/api/v1/project/{0}/'.format(self.project.id,), - content_type='application/json', - **self.post_auth - ) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/project/{0}/'.format( - self.project.id,) - ) - self.assertEquals(response.status_code, 404) - - -class ExecutableTest(FixtureTestCase): - - """Test Executable() API""" - - def setUp(self): - self.data = dict(name="Fibo", description="Fibonacci the Lame",) - # project is a ForeignKey and is not added automatically by tastypie - self.project = Project.objects.get(pk=1) - self.executable = Executable(project=self.project, **self.data) - self.executable.save() - self.client = Client() - super(ExecutableTest, self).setUp() - - def test_get_executable(self): - """Should get an existing executable""" - response = self.client.get('/api/v1/executable/{0}/'.format( - self.executable.id,)) - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], "{0}".format( - self.data['name'])) - - def test_get_executable_all_fields(self): - """Should get all fields for an executable""" - response = self.client.get('/api/v1/executable/%s/' % ( - self.executable.id,)) - self.assertEquals(response.status_code, 200) - for k in self.data.keys(): - self.assertEqual( - json.loads(response.content)[k], self.data[k]) - - def test_post(self): - """Should save a new executable""" - modified_data = copy.deepcopy(self.data) - modified_data['name'] = 'nbody' - modified_data['project'] = '/api/v1/project/{0}/'.format(self.project.pk) - response = self.client.post('/api/v1/executable/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 201) - response = self.client.get('/api/v1/executable/{0}/'.format( - self.executable.id)) - response_data = json.loads(response.content) - for k, v in self.data.items(): - self.assertEqual(response_data[k], v) - executable = Executable.objects.get(pk=int(response_data['id'])) - self.assertEquals(executable.project, self.project) - - def test_delete(self): - """Should delete an project""" - response = self.client.delete('/api/v1/executable/{0}/'.format( - self.executable.id,), content_type='application/json') - self.assertEquals(response.status_code, 204) - - response = self.client.get('/api/v1/executable/{0}/'.format( - self.executable.id,)) - - def test_nonexistent(self): - """Requesting an environment that doesn't exist should return a 404""" - response = self.client.get('/api/v1/environment/3333333/') - self.assertEquals(response.status_code, 404) - - -class BranchTest(FixtureTestCase): - - """Test Branch() API""" - - def setUp(self): - super(BranchTest, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_branch', 'codespeed', 'branch') - self.change = Permission.objects.get_by_natural_key( - 'change_branch', 'codespeed', 'branch') - self.delete = Permission.objects.get_by_natural_key( - 'delete_branch', 'codespeed', 'branch') - self.branch1 = Branch.objects.get(pk=1) - self.project_data = dict( - name="PyPy", - repo_type="M", - repo_path="ssh://hg@bitbucket.org/pypy/pypy", - repo_user="fridolin", - repo_pass="secret", - ) - self.project = Project(**self.project_data) - self.project.save() - self.branch2_data = dict( - name="master2", - project='/api/v1/project/{0}/'.format(self.project.id) - ) - self.client = Client() - - def test_get_branch(self): - """Should get an existing branch""" - response = self.client.get('/api/v1/branch/1/') - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], "default") - self.assertEqual(json.loads(response.content)['project'], - "/api/v1/project/1/") - - def test_get_branch_all_fields(self): - """Should get all fields for an branch""" - response = self.client.get('/api/v1/branch/%s/' % (self.branch1.id,)) - self.assertEquals(response.status_code, 200) - self.assertEquals(json.loads(response.content)['name'], - self.branch1.name) - self.assertEquals(json.loads(response.content)['project'], - '/api/v1/project/1/') - self.assertEquals(json.loads(response.content)['resource_uri'], - '/api/v1/branch/%s/' % (self.branch1.id,)) - - def test_post(self): - """Should save a new branch""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.branch2_data) - response = self.client.post('/api/v1/branch/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/branch/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - response = self.client.get('/api/v1/branch/{0}/'.format(id)) - for k, v in self.branch2_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - response = self.client.delete('/api/v1/branch/{0}/'.format(id), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - def test_put(self): - """Should modify an existing environment""" - request = HttpRequest() - request.user = self.api_user - request.user.user_permissions.add(self.change) - - modified_data = copy.deepcopy(self.branch2_data) - modified_data['name'] = "tip" - response = self.client.put('/api/v1/branch/1/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.put('/api/v1/branch/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/branch/1/') - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete a branch""" - request = HttpRequest() - request.user = self.api_user - request.user.user_permissions.add(self.delete) - - response = self.client.get('/api/v1/branch/1/') - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/branch/1/', - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.delete('/api/v1/branch/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - response = self.client.get('/api/v1/branch/1/') - self.assertEquals(response.status_code, 404) - - -class RevisionTest(FixtureTestCase): - - """Test Revision() API""" - - def setUp(self): - super(RevisionTest, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_revision', 'codespeed', 'revision') - self.change = Permission.objects.get_by_natural_key( - 'change_revision', 'codespeed', 'revision') - self.delete = Permission.objects.get_by_natural_key( - 'delete_revision', 'codespeed', 'revision') - DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S' - self.branch1 = Branch.objects.get(pk=1) - self.project1 = Project.objects.get(pk=1) - self.revision1_data = dict( - commitid="2a6306432e973cdcfd324e81169bb8029d47b736", - tag="tag", - date=datetime.now(), - message="Commit message\n - all bugs fixed\n - code 130% faster", - project=self.project1, - author="Alan T. ", - branch=self.branch1, - ) - self.revision1 = Revision(**self.revision1_data) - self.revision1.save() - self.revision2_data = dict( - commitid="4d3bea3cffe4edcd7d70fc46c5e19474cc4bd012", - tag="v1.0", - date=datetime.now().strftime(DATETIME_FORMAT), - message="Commit message\n - cleanup\n - all FIXMEs removed", - project='/api/v1/project/{0}/'.format(self.project1.id), - author="Chuck N. ", - branch='/api/v1/branch/{0}/'.format(self.branch1.id), - ) - self.client = Client() - - def test_get_revision(self): - """Should get an existing revision""" - response = self.client.get('/api/v1/revision/1/') - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['commitid'], "1") - self.assertEqual(json.loads(response.content)['project'], - "/api/v1/project/1/") - - def test_get_revision_all_fields(self): - """Should get all fields for a revision""" - response = self.client.get('/api/v1/revision/%s/' % (self.revision1.id,)) - self.assertEquals(response.status_code, 200) - self.assertEquals(json.loads(response.content)['commitid'], - self.revision1.commitid) - self.assertEquals(json.loads(response.content)['project'], - '/api/v1/project/%s/' % (self.project1.pk)) - self.assertEquals(json.loads(response.content)['branch'], - '/api/v1/branch/%s/' % (self.branch1.pk)) - self.assertEquals(json.loads(response.content)['tag'], - self.revision1_data['tag']) - self.assertEquals(json.loads(response.content)['message'], - self.revision1_data['message']) - - def test_post(self): - """Should save a new revision""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.revision2_data) - response = self.client.post('/api/v1/revision/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/revision/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - response = self.client.get('/api/v1/revision/{0}/'.format(id)) - for k, v in self.revision2_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - response = self.client.delete('/api/v1/revision/{0}/'.format(id), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - def test_put(self): - """Should modify an existing revision""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.revision2_data) - modified_data['tag'] = "v0.9.1" - response = self.client.put('/api/v1/revision/1/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.put('/api/v1/revision/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/revision/1/') - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete a revision""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.delete) - - response = self.client.get('/api/v1/revision/1/') - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/revision/1/', - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.delete('/api/v1/revision/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - response = self.client.get('/api/v1/revision/1/') - self.assertEquals(response.status_code, 404) - - -class ExecutableTest(FixtureTestCase): - - """Test Executable() API""" - - def setUp(self): - super(ExecutableTest, self).setUp() - - self.add = Permission.objects.get_by_natural_key( - 'add_executable', 'codespeed', 'executable') - self.change = Permission.objects.get_by_natural_key( - 'change_executable', 'codespeed', 'executable') - self.delete = Permission.objects.get_by_natural_key( - 'delete_executable', 'codespeed', 'executable') - - self.executable1 = Executable.objects.get(pk=1) - self.project1 = Project.objects.get(pk=1) - self.executable2_data = dict( - name="sleep", - description="Sleep benchmark", - project='/api/v1/project/{0}/'.format(self.project1.id), - ) - self.client = Client() - - def test_get_executable(self): - """Should get an existing executable""" - response = self.client.get('/api/v1/executable/1/') - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], - 'myexe O3 64bits') - self.assertEqual(json.loads(response.content)['project'], - "/api/v1/project/1/") - - def test_get_executable_all_fields(self): - """Should get all fields for an executable""" - response = self.client.get('/api/v1/executable/{0}/'.format( - self.executable1.id,)) - self.assertEquals(response.status_code, 200) - self.assertEquals(json.loads(response.content)['name'], - self.executable1.name) - self.assertEquals(json.loads(response.content)['project'], - '/api/v1/project/%s/' % (self.project1.pk)) - self.assertEquals(json.loads(response.content)['description'], - self.executable1.description) - - def test_post(self): - """Should save a new executable""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - modified_data = copy.deepcopy(self.executable2_data) - response = self.client.post('/api/v1/executable/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/executable/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - response = self.client.get('/api/v1/executable/{0}/'.format(id)) - for k, v in self.executable2_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - request.user.user_permissions.add(self.delete) - response = self.client.delete('/api/v1/executable/{0}/'.format(id), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - def test_put(self): - """Should modify an existing environment""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.executable2_data) - modified_data['name'] = "django" - response = self.client.put('/api/v1/executable/1/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.put('/api/v1/executable/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/executable/1/') - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete a executable""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.delete) - - response = self.client.get('/api/v1/executable/1/') - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/executable/1/', - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.delete('/api/v1/executable/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - response = self.client.get('/api/v1/executable/1/') - self.assertEquals(response.status_code, 404) - - -class BenchmarkTest(FixtureTestCase): - - """Test Benchmark() API""" - - def setUp(self): - super(BenchmarkTest, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_benchmark', 'codespeed', 'benchmark') - self.change = Permission.objects.get_by_natural_key( - 'change_benchmark', 'codespeed', 'benchmark') - self.delete = Permission.objects.get_by_natural_key( - 'delete_benchmark', 'codespeed', 'benchmark') - self.benchmark1 = Benchmark.objects.get(pk=1) - self.benchmark2_data = dict( - name="sleep", - benchmark_type='C', - description='fast faster fastest', - units_title='Time', - units='seconds', - lessisbetter=True, - default_on_comparison=True, - ) - self.benchmark2 = Benchmark(**self.benchmark2_data) - self.benchmark2.save() - self.client = Client() - - def test_get_benchmark(self): - """Should get an existing benchmark""" - response = self.client.get('/api/v1/benchmark/1/') - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['name'], - 'float') - self.assertEqual(json.loads(response.content)['units'], - "seconds") - - def test_get_benchmark_all_fields(self): - """Should get all fields for an benchmark""" - response = self.client.get('/api/v1/benchmark/{0}/'.format( - self.benchmark2.id,)) - self.assertEquals(response.status_code, 200) - for k, v in self.benchmark2_data.items(): - self.assertEqual(json.loads(response.content)[k], v) - - def test_post(self): - """Should save a new benchmark""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - modified_data = copy.deepcopy(self.benchmark2_data) - modified_data['name'] = 'wake' - response = self.client.post('/api/v1/benchmark/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.post('/api/v1/benchmark/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth - ) - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - response = self.client.get('/api/v1/benchmark/{0}/'.format(id)) - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - request.user.user_permissions.add(self.delete) - response = self.client.delete('/api/v1/benchmark/{0}/'.format(id), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - def test_put(self): - """Should modify an existing benchmark""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.benchmark2_data) - modified_data['name'] = "django" - response = self.client.put('/api/v1/benchmark/1/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.put('/api/v1/benchmark/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - response = self.client.get('/api/v1/benchmark/1/') - for k, v in modified_data.items(): - self.assertEqual( - json.loads(response.content)[k], v) - - def test_delete(self): - """Should delete a benchmark""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.delete) - response = self.client.get('/api/v1/benchmark/1/') - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/benchmark/1/', - content_type='application/json') - self.assertEquals(response.status_code, 401) - response = self.client.delete('/api/v1/benchmark/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 204) - - response = self.client.get('/api/v1/benchmark/1/') - self.assertEquals(response.status_code, 404) - - -class ReportTest(FixtureTestCase): - - """Test Report() API""" - - def setUp(self): - super(ReportTest, self).setUp() - self.add = Permission.objects.get_by_natural_key( - 'add_report', 'codespeed', 'report') - self.change = Permission.objects.get_by_natural_key( - 'change_report', 'codespeed', 'report') - self.delete = Permission.objects.get_by_natural_key( - 'delete_report', 'codespeed', 'report') - - self.report1 = Report.objects.get(pk=1) - self.revision1 = Revision.objects.get(pk=1) - self.executable1 = Executable.objects.get(pk=1) - self.environment1 = Environment.objects.get(pk=1) - self.executable2_data = dict( - name="Fibo", - description="Fibonacci the Lame", - ) - self.project = Project.objects.get(pk=1) - self.executable2 = Executable(project=self.project, - **self.executable2_data) - self.executable2.save() - self.report2_data = dict( - revision=self.revision1, - environment=self.environment1, - executable=self.executable2, - ) - self.report2 = Report(**self.report2_data) - self.report2.save() - self.report2_data = dict( - revision='/api/v1/revision/{0}/'.format(self.revision1.id), - environment='/api/v1/environment/{0}/'.format(self.environment1.id), - executable='/api/v1/executable/{0}/'.format(self.executable2.id), - ) - self.client = Client() - - def test_get_report(self): - """Should get an existing report""" - response = self.client.get('/api/v1/report/1/') - self.assertEquals(response.status_code, 200) - self.assertEqual(json.loads(response.content)['summary'], - 'float -50.0%') - self.assertEqual(json.loads(response.content)['colorcode'], - "green") - - def test_get_report_all_fields(self): - """Should get all fields for an report""" - response = self.client.get('/api/v1/report/{0}/'.format( - self.report2.id,)) - self.assertEquals(response.status_code, 200) - for k, v in self.report2_data.items(): - self.assertEqual(json.loads(response.content)[k], v) - - def test_post(self): - """Should not save a new report""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - modified_data = copy.deepcopy(self.report2_data) - response = self.client.post('/api/v1/report/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 405) - # next has to be 405 (method not allowed), - # otherwise would raise IntegrityError - response = self.client.post('/api/v1/report/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - # next has to be 405, otherwise would raise IntegrityError - self.assertEquals(response.status_code, 405) - - def test_put(self): - """Should not modify an existing report""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - request.user.user_permissions.add(self.change) - request.user.user_permissions.add(self.delete) - - modified_data = copy.deepcopy(self.report2_data) - response = self.client.put('/api/v1/report/1/', - data=json.dumps(modified_data), - content_type='application/json') - self.assertEquals(response.status_code, 405) - response = self.client.put('/api/v1/report/1/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 405) - - def test_delete(self): - """Should delete a report""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.delete) - - response = self.client.get('/api/v1/report/1/') - self.assertEquals(response.status_code, 200) - # from fixture - response = self.client.delete('/api/v1/report/1/', - content_type='application/json') - self.assertEquals(response.status_code, 405) - response = self.client.delete('/api/v1/report/1/', - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 405) - - -class ResultBundleTestCase(FixtureTestCase): - - """Test CRUD of results via API""" - - def setUp(self): - super(ResultBundleTestCase, self).setUp() - self.request = HttpRequest() - self.request.user = self.api_user - self.data1 = { - 'commitid': '/api/v1/revision/2/', - 'branch': '/api/v1/branch/1/', # Always use default for trunk/master/tip - 'project': '/api/v1/project/2/', - 'executable': '/api/v1/executable/1/', - 'benchmark': '/api/v1/benchmark/1/', - 'environment': '/api/v1/environment/2/', - 'result_value': 4000, - } - DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' - self.data_optional = { - 'std_dev': 0.2, - 'val_min': 2.23, - 'val_max': 3.42, - 'date': datetime.now().strftime(DATETIME_FORMAT), - } - project_data = dict( - name="PyPy", - repo_type="M", - repo_path="ssh://hg@bitbucket.org/pypy/pypy", - repo_user="fridolin", - repo_pass="secret", - ) - self.project = Project(**project_data) - self.project.save() - self.env1 = Environment(name='Bulldozer') - self.env1.save() - - def test_populate_and_save(self): - """Should populate ResultBundle() with data""" - bundle = ResultBundle(request=self.request, **self.data1) - bundle._populate_obj_by_data() - # should raise exception if not OK - bundle.hydrate_and_save() - self.assert_(True) - - def test_save_same_result_again(self): - """Save a previously saved result. Expected is an IntegrityError""" - modified_data = copy.deepcopy(self.data1) - modified_data['environment'] = '/api/v1/environment/1/' - modified_data['project'] = '/api/v1/project/1/' - bundle = ResultBundle(request=self.request, **modified_data) - bundle._populate_obj_by_data() - self.assertRaises(IntegrityError, bundle.hydrate_and_save) - - def test_for_nonexistent_environment(self): - """Save data using non existing environment. Expected is an - ImmediateHttpResponse - """ - modified_data = copy.deepcopy(self.data1) - modified_data['environment'] = '/api/v1/environment/3/' - self.assertRaises(ImmediateHttpResponse, ResultBundle, **modified_data) - - def test_insufficient_data(self): - """See if Result() is saved w/ insufficient data""" - modified_data = copy.deepcopy(self.data1) - modified_data.pop('environment') - self.assertRaises(ImmediateHttpResponse, ResultBundle, **modified_data) - - def test_date_attr_set(self): - """Should add date attr to Result() obj if date is not given""" - # date is set automatically - modified_data = copy.deepcopy(self.data1) - bundle = ResultBundle(request=self.request, **modified_data) - bundle.hydrate_and_save() - self.assertIsInstance(bundle.obj.date, datetime) - # date set by value - modified_data['date'] = '2011-05-05 03:01:45' - bundle = ResultBundle(request=self.request, **modified_data) - # wrong date string - modified_data['date'] = '2011-05-05T03:01:45' - self.assertRaises(ImmediateHttpResponse, ResultBundle, **modified_data) - - def test_optional_data(self): - """Should save optional data.""" - data = dict(self.data1.items() + self.data_optional.items()) - bundle = ResultBundle(request=self.request, **data) - bundle.hydrate_and_save() - self.assertIsInstance(bundle.obj.date, datetime) - self.assertEqual(bundle.obj.std_dev, - float(self.data_optional['std_dev'])) - self.assertEqual(bundle.obj.val_max, - float(self.data_optional['val_max'])) - self.assertEqual(bundle.obj.val_min, - float(self.data_optional['val_min'])) - - -class ResultBundleResourceTestCase(FixtureTestCase): - - """Submitting new benchmark results""" - - DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' - - def setUp(self): - super(ResultBundleResourceTestCase, self).setUp() - - self.add = Permission.objects.get_by_natural_key( - 'add_result', 'codespeed', 'result') - self.change = Permission.objects.get_by_natural_key( - 'change_result', 'codespeed', 'result') - self.delete = Permission.objects.get_by_natural_key( - 'delete_result', 'codespeed', 'result') - - self.data1 = { - 'commitid': '/api/v1/revision/2/', - 'branch': '/api/v1/branch/1/', # Always use default for trunk/master/tip - 'project': '/api/v1/project/2/', - 'executable': '/api/v1/executable/1/', - 'benchmark': '/api/v1/benchmark/1/', - 'environment': '/api/v1/environment/2/', - 'result_value': 4000, - } - self.data_optional = { - 'std_dev': 0.2, - 'val_min': 2.23, - 'val_max': 3.42, - 'date': datetime.now().strftime(self.DATETIME_FORMAT), - } - project_data = dict( - name="PyPy", - repo_type="M", - repo_path="ssh://hg@bitbucket.org/pypy/pypy", - repo_user="fridolin", - repo_pass="secret", - ) - self.project = Project(**project_data) - self.project.save() - self.env1 = Environment(name='Bulldozer') - self.env1.save() - self.client = Client() - - def test_post_mandatory(self): - """Should save a new result with only mandatory data""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - response = self.client.post('/api/v1/benchmark-result/', - data=json.dumps(self.data1), - content_type='application/json') - self.assertEquals(response.status_code, 201) - id = response['Location'].rsplit('/', 2)[-2] - result = Result.objects.get(pk=int(id)) - # just to make the point - self.assertIsInstance(result, Result) - self.assertEqual(result.value, self.data1['result_value']) - - def test_post_all_data(self): - """Should save a new result with mandatory and optional data""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - data = dict(self.data1, **self.data_optional) - response = self.client.post('/api/v1/benchmark-result/', - data=json.dumps(data), - content_type='application/json') - self.assertEquals(response.status_code, 201) - - def test_post_invalid_data(self): - """Should save a new result with mandatory and optional data""" - request = HttpRequest() - request.user = self.api_user - - request.user.user_permissions.add(self.add) - - modified_data = copy.deepcopy(self.data1) - # environment does not exist - modified_data['environment'] = '/api/v1/environment/5/' - response = self.client.post('/api/v1/benchmark-result/', - data=json.dumps(modified_data), - content_type='application/json', - **self.post_auth) - self.assertEquals(response.status_code, 400) - - def test_get_one(self): - """Should get a result bundle""" - response = self.client.get('/api/v1/benchmark-result/1/', - content_type='application/json') - self.assertEquals(response.status_code, 200) - response_data = json.loads(response.content) - for k in ('project', 'result', 'branch', 'benchmark', 'environment', - 'executable', 'revision'): - self.assertEqual( - response_data[k], - '/api/v1/{0}/1/'.format(k,)) - - -#def suite(): -# suite = unittest.TestSuite() -# suite.addTest(EnvironmentTest()) -# return suite diff --git a/codespeed/urls.py b/codespeed/urls.py index f954ecdc..38ab4e53 100644 --- a/codespeed/urls.py +++ b/codespeed/urls.py @@ -2,28 +2,11 @@ from django.conf.urls.defaults import * from django.core.urlresolvers import reverse from django.views.generic import TemplateView -from tastypie.api import Api from codespeed.feeds import LatestEntries -from codespeed.api import (UserResource, EnvironmentResource, - ProjectResource, ExecutableResource, ReportResource, - BenchmarkResource, ResultResource, BranchResource, - RevisionResource, ResultBundleResource) feeds = {'latest': LatestEntries} -rest_api = Api(api_name='v1') -rest_api.register(EnvironmentResource()) -rest_api.register(UserResource()) -rest_api.register(ProjectResource()) -rest_api.register(ExecutableResource()) -rest_api.register(BenchmarkResource()) -rest_api.register(ResultResource()) -rest_api.register(BranchResource()) -rest_api.register(RevisionResource()) -rest_api.register(ReportResource()) -rest_api.register(ResultBundleResource()) - urlpatterns = patterns('', (r'^$', TemplateView.as_view(template_name='home.html')), (r'^about/$', TemplateView.as_view(template_name='about.html')), @@ -46,5 +29,4 @@ # URLs for adding results (r'^result/add/json/$', 'add_json_results'), (r'^result/add/$', 'add_result'), - (r'^api/', include(rest_api.urls)), ) diff --git a/sample_project/settings.py b/sample_project/settings.py index 52ad668b..02228c25 100644 --- a/sample_project/settings.py +++ b/sample_project/settings.py @@ -113,12 +113,10 @@ def process_exception(self, request, exception): 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', - #'django.contrib.sites', 'django.contrib.admin', 'django.contrib.staticfiles', 'codespeed', 'south', - 'tastypie', ) SOUTH_TESTS_MIGRATE = False diff --git a/sample_project/urls.py b/sample_project/urls.py index e9a33c93..6a5ddce3 100644 --- a/sample_project/urls.py +++ b/sample_project/urls.py @@ -12,17 +12,7 @@ urlpatterns = patterns('', (r'^admin/', include(admin.site.urls)), -) - -urlpatterns += patterns( - '', - #('^$', RedirectView.as_view(url='/speed/')), -) - -urlpatterns += patterns( - '', (r'^', include('codespeed.urls')), - #(r'^speed/', include('codespeed.urls')), ) if settings.DEBUG: From 05c001328eb22fc7aa1fc606097631453a2205af Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Wed, 13 Nov 2013 19:22:04 +0100 Subject: [PATCH 002/226] Remove tastypie from requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ed569aec..821662d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ Django>=1.4 South<=2.0 isodate==0.4.8 -django-tastypie==0.10.0 From f3eef2c84214e4e0d477a6a9a6122d935fefb69c Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Thu, 14 Nov 2013 21:03:44 +0100 Subject: [PATCH 003/226] Added config for Travis CI, and adapted README.md Signed-off-by: Stefan Marr --- .travis.yml | 26 ++++++++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 30 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..0fd60054 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: python +python: + - "2.5" + - "2.6" + - "2.7" + - "3.2" + - "3.3" + # does not have headers provided, please ask https://launchpad.net/~pypy/+archive/ppa + # maintainers to fix their pypy-dev package. + - "pypy" + +env: + - DJANGO_VERSION=1.4.4 + - DJANGO_VERSION=1.5.1 + - DJANGO_VERSION=1.6 + +# command to install dependencies +install: + - pip install -q Django==$DJANGO_VERSION + - python setup.py install + +# command to run tests +script: + - python setup.py test + - cd sample_project + - python manage.py test diff --git a/README.md b/README.md index 7a1e652d..cb2a036c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Known to be used by [PyPy](http://speed.pypy.org), [Twisted](http://speed.twiste For an overview of some application concepts see the [wiki page](https://github.com/tobami/codespeed/wiki/Overview) +# Build Status + +The current build status on Travis CI is: [![Build Status](https://travis-ci.org/tobami/codespeed.png?branch=master)](https://travis-ci.org/tobami/codespeed) + # Requirements You will need Python 2.6+ and Django 1.3+ with South isodate and Tastypie. From e0532ad45ed7c7ef66999bf5d8cbccc12f328f3f Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Thu, 14 Nov 2013 21:11:06 +0100 Subject: [PATCH 004/226] Reduce test matrix to main versions of Python Signed-off-by: Stefan Marr --- .travis.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0fd60054..e4f50e54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: python python: - - "2.5" - - "2.6" +# - "2.5" +# - "2.6" - "2.7" - - "3.2" +# - "3.2" - "3.3" - # does not have headers provided, please ask https://launchpad.net/~pypy/+archive/ppa - # maintainers to fix their pypy-dev package. - "pypy" env: From dd6ce096b41232b508ffb21258731ac126dbb633 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Thu, 14 Nov 2013 21:16:40 +0100 Subject: [PATCH 005/226] Removed django-tastypie from setup.py This was missing from the tastypie removal commit. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6ec79291..8628e539 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ version='0.9.1', url='https://github.com/tobami/codespeed', license='GNU Lesser General Public License version 2.1', - install_requires=['django>=1.4', 'isodate', 'south<=2.0', 'django-tastypie'], + install_requires=['django>=1.4', 'isodate', 'south<=2.0'], packages=find_packages(exclude=['ez_setup', 'sample_project']), description='A web application to monitor and analyze the performance of your code', include_package_data=True, From fc4a95fa6b3054d7b9cea1384cce2276c822ae68 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Sat, 16 Nov 2013 21:18:34 +0100 Subject: [PATCH 006/226] Restrict testing to Python 2.7 Signed-off-by: Stefan Marr --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4f50e54..868055c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ python: # - "2.6" - "2.7" # - "3.2" - - "3.3" - - "pypy" +# - "3.3" +# - "pypy" env: - DJANGO_VERSION=1.4.4 From 1302e7d5a71486a0dc546d07682274b7f77195c9 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Sat, 16 Nov 2013 21:19:13 +0100 Subject: [PATCH 007/226] Show build status directly under the title, without text Signed-off-by: Stefan Marr --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index cb2a036c..c62d0fe4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Codespeed +[![Build Status](https://travis-ci.org/tobami/codespeed.png?branch=master)](https://travis-ci.org/tobami/codespeed) A web application to monitor and analyze the performance of your code. @@ -6,10 +7,6 @@ Known to be used by [PyPy](http://speed.pypy.org), [Twisted](http://speed.twiste For an overview of some application concepts see the [wiki page](https://github.com/tobami/codespeed/wiki/Overview) -# Build Status - -The current build status on Travis CI is: [![Build Status](https://travis-ci.org/tobami/codespeed.png?branch=master)](https://travis-ci.org/tobami/codespeed) - # Requirements You will need Python 2.6+ and Django 1.3+ with South isodate and Tastypie. From a17a59cd49e181e13f8a880a4a15882df7274e77 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 17 Nov 2013 18:07:03 +0100 Subject: [PATCH 008/226] Add test fixtures to build --- .travis.yml | 4 +- MANIFEST.in | 1 + codespeed/fixtures/gettimeline_unittest.json | 1 - codespeed/fixtures/timeline_tests.json | 447 +++++++++++++++++++ codespeed/tests/tests.py | 20 +- 5 files changed, 457 insertions(+), 16 deletions(-) delete mode 100644 codespeed/fixtures/gettimeline_unittest.json create mode 100644 codespeed/fixtures/timeline_tests.json diff --git a/.travis.yml b/.travis.yml index 868055c4..0bb30f4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: - DJANGO_VERSION=1.4.4 - DJANGO_VERSION=1.5.1 - DJANGO_VERSION=1.6 - + # command to install dependencies install: - pip install -q Django==$DJANGO_VERSION @@ -21,4 +21,4 @@ install: script: - python setup.py test - cd sample_project - - python manage.py test + - python manage.py test codespeed diff --git a/MANIFEST.in b/MANIFEST.in index 3e90a87d..70e9ce36 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include README.md include LICENSE +recursive-include codespeed/fixtures timeline_tests.json recursive-include codespeed/templates/codespeed * recursive-include codespeed/static * diff --git a/codespeed/fixtures/gettimeline_unittest.json b/codespeed/fixtures/gettimeline_unittest.json deleted file mode 100644 index 48952e20..00000000 --- a/codespeed/fixtures/gettimeline_unittest.json +++ /dev/null @@ -1 +0,0 @@ -[{"pk": 1, "model": "codespeed.project", "fields": {"repo_type": "N", "name": "MyProject", "track": true, "repo_user": "", "repo_pass": "", "repo_path": ""}}, {"pk": 2, "model": "codespeed.project", "fields": {"repo_type": "N", "name": "Other", "track": true, "repo_user": "", "repo_pass": "", "repo_path": ""}}, {"pk": 3, "model": "codespeed.project", "fields": {"repo_type": "N", "name": "BaseProj", "track": false, "repo_user": "", "repo_pass": "", "repo_path": ""}}, {"pk": 1, "model": "codespeed.branch", "fields": {"project": 1, "name": "default"}}, {"pk": 2, "model": "codespeed.branch", "fields": {"project": 1, "name": "feature"}}, {"pk": 3, "model": "codespeed.branch", "fields": {"project": 2, "name": "default"}}, {"pk": 4, "model": "codespeed.branch", "fields": {"project": 3, "name": "default"}}, {"pk": 1, "model": "codespeed.revision", "fields": {"project": 1, "author": "", "commitid": "1", "tag": "", "branch": 1, "date": "2011-04-12 16:43:20", "message": ""}}, {"pk": 2, "model": "codespeed.revision", "fields": {"project": 1, "author": "", "commitid": "2", "tag": "", "branch": 1, "date": "2011-04-13 17:04:22", "message": ""}}, {"pk": 3, "model": "codespeed.revision", "fields": {"project": 1, "author": "", "commitid": "3", "tag": "", "branch": 2, "date": "2011-04-13 12:03:38", "message": ""}}, {"pk": 4, "model": "codespeed.revision", "fields": {"project": 1, "author": "", "commitid": "4", "tag": "", "branch": 2, "date": "2011-04-14 18:04:51", "message": ""}}, {"pk": 5, "model": "codespeed.revision", "fields": {"project": 1, "author": "", "commitid": "5", "tag": "", "branch": 1, "date": "2011-04-14 19:13:05", "message": ""}}, {"pk": 6, "model": "codespeed.revision", "fields": {"project": 2, "author": "", "commitid": "1", "tag": "", "branch": 3, "date": "2011-04-13 12:05:11", "message": ""}}, {"pk": 7, "model": "codespeed.revision", "fields": {"project": 2, "author": "", "commitid": "2", "tag": "", "branch": 3, "date": "2011-04-14 12:05:22", "message": ""}}, {"pk": 8, "model": "codespeed.revision", "fields": {"project": 3, "author": "", "commitid": "444", "tag": "1.0", "branch": 4, "date": "2011-05-24 09:37:32", "message": ""}}, {"pk": 1, "model": "codespeed.executable", "fields": {"project": 1, "name": "myexe O3 64bits", "description": ""}}, {"pk": 2, "model": "codespeed.executable", "fields": {"project": 2, "name": "exe", "description": ""}}, {"pk": 3, "model": "codespeed.executable", "fields": {"project": 3, "name": "baseExe", "description": ""}}, {"pk": 1, "model": "codespeed.benchmark", "fields": {"name": "float", "benchmark_type": "C", "units_title": "Time", "units": "seconds", "lessisbetter": true, "description": ""}}, {"pk": 2, "model": "codespeed.benchmark", "fields": {"name": "int", "benchmark_type": "C", "units_title": "Time", "units": "seconds", "lessisbetter": true, "description": ""}}, {"pk": 1, "model": "codespeed.environment", "fields": {"kernel": "", "memory": "", "os": "", "name": "Dual Core", "cpu": ""}}, {"pk": 1, "model": "codespeed.result", "fields": {"executable": 1, "benchmark": 1, "value": 4000.0, "environment": 1, "val_min": 3995.0999999999999, "std_dev": 1.11111, "date": "2011-04-13 19:04:00", "val_max": 4001.5999999999999, "revision": 1}}, {"pk": 2, "model": "codespeed.result", "fields": {"executable": 1, "benchmark": 1, "value": 2000.0, "environment": 1, "val_min": 1995.0999999999999, "std_dev": 1.11111, "date": "2011-04-13 17:06:19", "val_max": 2001.5999999999999, "revision": 2}}, {"pk": 3, "model": "codespeed.result", "fields": {"executable": 1, "benchmark": 1, "value": 2100.0, "environment": 1, "val_min": 995.10000000000002, "std_dev": 1.11111, "date": "2011-04-13 17:06:47", "val_max": 1001.6, "revision": 5}}, {"pk": 4, "model": "codespeed.result", "fields": {"executable": 1, "benchmark": 1, "value": 1800.0, "environment": 1, "val_min": 3995.0999999999999, "std_dev": 1.11111, "date": "2011-04-13 19:06:11", "val_max": 4001.5999999999999, "revision": 3}}, {"pk": 5, "model": "codespeed.result", "fields": {"executable": 2, "benchmark": 1, "value": 1000.0, "environment": 1, "val_min": 995.10000000000002, "std_dev": 1.11111, "date": "2011-04-13 19:11:50", "val_max": 1001.6, "revision": 6}}, {"pk": 6, "model": "codespeed.result", "fields": {"executable": 1, "benchmark": 1, "value": 3000.0, "environment": 1, "val_min": 2995.0999999999999, "std_dev": 1.11111, "date": "2011-04-13 19:06:58", "val_max": 3001.5999999999999, "revision": 4}}, {"pk": 7, "model": "codespeed.result", "fields": {"executable": 2, "benchmark": 1, "value": 500.0, "environment": 1, "val_min": 495.10000000000002, "std_dev": 1.11111, "date": "2011-04-13 19:12:23", "val_max": 501.60000000000002, "revision": 7}}, {"pk": 8, "model": "codespeed.result", "fields": {"executable": 3, "benchmark": 1, "value": 1850.0, "environment": 1, "val_min": null, "std_dev": null, "date": "2011-05-24 09:38:18", "val_max": null, "revision": 8}}, {"pk": 1, "model": "codespeed.report", "fields": {"executable": 1, "colorcode": "green", "summary": "float -50.0%", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", "revision": 2}}, {"pk": 2, "model": "codespeed.report", "fields": {"executable": 1, "colorcode": "green", "summary": "float -50.0%", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 995.10000000000002, \"std_dev\": 1.11111, \"result\": 1000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 1001.6, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", "revision": 5}}, {"pk": 3, "model": "codespeed.report", "fields": {"executable": 1, "colorcode": "green", "summary": "float -50.0%", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", "revision": 3}}, {"pk": 4, "model": "codespeed.report", "fields": {"executable": 2, "colorcode": "none", "summary": "", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 995.10000000000002, \"std_dev\": 1.11111, \"result\": 1000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 1001.6, \"change\": \"-\"}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": \"-\"}}]", "revision": 6}}, {"pk": 5, "model": "codespeed.report", "fields": {"executable": 1, "colorcode": "none", "summary": "", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 3995.0999999999999, \"std_dev\": 1.11111, \"result\": 4000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 4001.5999999999999, \"change\": \"-\"}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": \"-\"}}]", "revision": 1}}, {"pk": 6, "model": "codespeed.report", "fields": {"executable": 1, "colorcode": "green", "summary": "float -50.0%", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", "revision": 4}}, {"pk": 7, "model": "codespeed.report", "fields": {"executable": 2, "colorcode": "green", "summary": "float -50.0%", "environment": 1, "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 495.10000000000002, \"std_dev\": 1.11111, \"result\": 500.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 501.60000000000002, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", "revision": 7}}] \ No newline at end of file diff --git a/codespeed/fixtures/timeline_tests.json b/codespeed/fixtures/timeline_tests.json new file mode 100644 index 00000000..0292f39b --- /dev/null +++ b/codespeed/fixtures/timeline_tests.json @@ -0,0 +1,447 @@ +[ + { + "pk": 1, + "model": "codespeed.project", + "fields": { + "repo_type": "N", + "name": "MyProject", + "commit_browsing_url": "", + "repo_user": "", + "track": true, + "repo_pass": "", + "repo_path": "" + } + }, + { + "pk": 2, + "model": "codespeed.project", + "fields": { + "repo_type": "N", + "name": "Other", + "commit_browsing_url": "", + "repo_user": "", + "track": true, + "repo_pass": "", + "repo_path": "" + } + }, + { + "pk": 3, + "model": "codespeed.project", + "fields": { + "repo_type": "N", + "name": "BaseProj", + "commit_browsing_url": "", + "repo_user": "", + "track": false, + "repo_pass": "", + "repo_path": "" + } + }, + { + "pk": 1, + "model": "codespeed.branch", + "fields": { + "project": 1, + "name": "default" + } + }, + { + "pk": 2, + "model": "codespeed.branch", + "fields": { + "project": 1, + "name": "feature" + } + }, + { + "pk": 3, + "model": "codespeed.branch", + "fields": { + "project": 2, + "name": "default" + } + }, + { + "pk": 4, + "model": "codespeed.branch", + "fields": { + "project": 3, + "name": "default" + } + }, + { + "pk": 1, + "model": "codespeed.revision", + "fields": { + "project": 1, + "author": "", + "commitid": "1", + "tag": "", + "branch": 1, + "date": "2011-04-12T16:43:20", + "message": "" + } + }, + { + "pk": 2, + "model": "codespeed.revision", + "fields": { + "project": 1, + "author": "", + "commitid": "2", + "tag": "", + "branch": 1, + "date": "2011-04-13T17:04:22", + "message": "" + } + }, + { + "pk": 3, + "model": "codespeed.revision", + "fields": { + "project": 1, + "author": "", + "commitid": "3", + "tag": "", + "branch": 2, + "date": "2011-04-13T12:03:38", + "message": "" + } + }, + { + "pk": 4, + "model": "codespeed.revision", + "fields": { + "project": 1, + "author": "", + "commitid": "4", + "tag": "", + "branch": 2, + "date": "2011-04-14T18:04:51", + "message": "" + } + }, + { + "pk": 5, + "model": "codespeed.revision", + "fields": { + "project": 1, + "author": "", + "commitid": "5", + "tag": "", + "branch": 1, + "date": "2011-04-14T19:13:05", + "message": "" + } + }, + { + "pk": 6, + "model": "codespeed.revision", + "fields": { + "project": 2, + "author": "", + "commitid": "1", + "tag": "", + "branch": 3, + "date": "2011-04-13T12:05:11", + "message": "" + } + }, + { + "pk": 7, + "model": "codespeed.revision", + "fields": { + "project": 2, + "author": "", + "commitid": "2", + "tag": "", + "branch": 3, + "date": "2011-04-14T12:05:22", + "message": "" + } + }, + { + "pk": 8, + "model": "codespeed.revision", + "fields": { + "project": 3, + "author": "", + "commitid": "444", + "tag": "1.0", + "branch": 4, + "date": "2011-05-24T09:37:32", + "message": "" + } + }, + { + "pk": 1, + "model": "codespeed.executable", + "fields": { + "project": 1, + "name": "myexe O3 64bits", + "description": "" + } + }, + { + "pk": 2, + "model": "codespeed.executable", + "fields": { + "project": 2, + "name": "exe", + "description": "" + } + }, + { + "pk": 3, + "model": "codespeed.executable", + "fields": { + "project": 3, + "name": "baseExe", + "description": "" + } + }, + { + "pk": 1, + "model": "codespeed.benchmark", + "fields": { + "parent": null, + "name": "float", + "benchmark_type": "C", + "default_on_comparison": true, + "units_title": "Time", + "units": "seconds", + "lessisbetter": true, + "description": "" + } + }, + { + "pk": 2, + "model": "codespeed.benchmark", + "fields": { + "parent": null, + "name": "int", + "benchmark_type": "C", + "default_on_comparison": true, + "units_title": "Time", + "units": "seconds", + "lessisbetter": true, + "description": "" + } + }, + { + "pk": 1, + "model": "codespeed.environment", + "fields": { + "kernel": "", + "memory": "", + "os": "", + "name": "Dual Core", + "cpu": "" + } + }, + { + "pk": 1, + "model": "codespeed.result", + "fields": { + "executable": 1, + "benchmark": 1, + "value": 4000.0, + "environment": 1, + "val_min": 3995.1, + "std_dev": 1.11111, + "date": "2011-04-13T19:04:00", + "val_max": 4001.6, + "revision": 1 + } + }, + { + "pk": 2, + "model": "codespeed.result", + "fields": { + "executable": 1, + "benchmark": 1, + "value": 2000.0, + "environment": 1, + "val_min": 1995.1, + "std_dev": 1.11111, + "date": "2011-04-13T17:06:19", + "val_max": 2001.6, + "revision": 2 + } + }, + { + "pk": 3, + "model": "codespeed.result", + "fields": { + "executable": 1, + "benchmark": 1, + "value": 2100.0, + "environment": 1, + "val_min": 995.1, + "std_dev": 1.11111, + "date": "2011-04-13T17:06:47", + "val_max": 1001.6, + "revision": 5 + } + }, + { + "pk": 4, + "model": "codespeed.result", + "fields": { + "executable": 1, + "benchmark": 1, + "value": 1800.0, + "environment": 1, + "val_min": 3995.1, + "std_dev": 1.11111, + "date": "2011-04-13T19:06:11", + "val_max": 4001.6, + "revision": 3 + } + }, + { + "pk": 5, + "model": "codespeed.result", + "fields": { + "executable": 2, + "benchmark": 1, + "value": 1000.0, + "environment": 1, + "val_min": 995.1, + "std_dev": 1.11111, + "date": "2011-04-13T19:11:50", + "val_max": 1001.6, + "revision": 6 + } + }, + { + "pk": 6, + "model": "codespeed.result", + "fields": { + "executable": 1, + "benchmark": 1, + "value": 3000.0, + "environment": 1, + "val_min": 2995.1, + "std_dev": 1.11111, + "date": "2011-04-13T19:06:58", + "val_max": 3001.6, + "revision": 4 + } + }, + { + "pk": 7, + "model": "codespeed.result", + "fields": { + "executable": 2, + "benchmark": 1, + "value": 500.0, + "environment": 1, + "val_min": 495.1, + "std_dev": 1.11111, + "date": "2011-04-13T19:12:23", + "val_max": 501.6, + "revision": 7 + } + }, + { + "pk": 8, + "model": "codespeed.result", + "fields": { + "executable": 3, + "benchmark": 1, + "value": 1850.0, + "environment": 1, + "val_min": null, + "std_dev": null, + "date": "2011-05-24T09:38:18", + "val_max": null, + "revision": 8 + } + }, + { + "pk": 1, + "model": "codespeed.report", + "fields": { + "executable": 1, + "colorcode": "green", + "summary": "float -50.0%", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", + "revision": 2 + } + }, + { + "pk": 2, + "model": "codespeed.report", + "fields": { + "executable": 1, + "colorcode": "green", + "summary": "float -50.0%", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 995.10000000000002, \"std_dev\": 1.11111, \"result\": 1000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 1001.6, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", + "revision": 5 + } + }, + { + "pk": 3, + "model": "codespeed.report", + "fields": { + "executable": 1, + "colorcode": "green", + "summary": "float -50.0%", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", + "revision": 3 + } + }, + { + "pk": 4, + "model": "codespeed.report", + "fields": { + "executable": 2, + "colorcode": "none", + "summary": "", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 995.10000000000002, \"std_dev\": 1.11111, \"result\": 1000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 1001.6, \"change\": \"-\"}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": \"-\"}}]", + "revision": 6 + } + }, + { + "pk": 5, + "model": "codespeed.report", + "fields": { + "executable": 1, + "colorcode": "none", + "summary": "", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 3995.0999999999999, \"std_dev\": 1.11111, \"result\": 4000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 4001.5999999999999, \"change\": \"-\"}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": \"-\"}}]", + "revision": 1 + } + }, + { + "pk": 6, + "model": "codespeed.report", + "fields": { + "executable": 1, + "colorcode": "green", + "summary": "float -50.0%", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 1995.0999999999999, \"std_dev\": 1.11111, \"result\": 2000.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 2001.5999999999999, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", + "revision": 4 + } + }, + { + "pk": 7, + "model": "codespeed.report", + "fields": { + "executable": 2, + "colorcode": "green", + "summary": "float -50.0%", + "environment": 1, + "_tablecache": "[{\"units_title\": \"Time\", \"rows\": [{\"bench_name\": \"float\", \"val_min\": 495.10000000000002, \"std_dev\": 1.11111, \"result\": 500.0, \"trend\": \"-\", \"bench_description\": \"\", \"val_max\": 501.60000000000002, \"change\": -50.0}], \"hasmax\": true, \"precission\": 2, \"units\": \"seconds\", \"has_stddev\": true, \"hasmin\": true, \"lessisbetter\": true, \"totals\": {\"trend\": \"-\", \"change\": -50.0}}]", + "revision": 7 + } + } +] \ No newline at end of file diff --git a/codespeed/tests/tests.py b/codespeed/tests/tests.py index 64288fc6..bb584d35 100644 --- a/codespeed/tests/tests.py +++ b/codespeed/tests/tests.py @@ -5,7 +5,6 @@ import os from django.test import TestCase -from django.test.client import Client from django.core.urlresolvers import reverse from django.conf import settings @@ -15,11 +14,10 @@ from codespeed import settings as default_settings -class AddResult(TestCase): +class TestAddResult(TestCase): def setUp(self): self.path = reverse('codespeed.views.add_result') - self.client = Client() self.e = Environment(name='Dual Core', cpu='Core 2 Duo 8200') self.e.save() temp = datetime.today() @@ -162,10 +160,10 @@ def test_add_result_with_no_project(self): self.assertEquals(response.content, "Result data saved successfully") -class AddJSONResults(TestCase): +class TestAddJSONResults(TestCase): + def setUp(self): self.path = reverse('codespeed.views.add_json_results') - self.client = Client() self.e = Environment(name='bigdog', cpu='Core 2 Duo 8200') self.e.save() temp = datetime.today() @@ -306,11 +304,8 @@ def test_report_is_created(self): self.assertEquals(number_of_reports, 1) -class Timeline(TestCase): - fixtures = ["gettimeline_unittest.json"] - - def setUp(self): - self.client = Client() +class TestTimeline(TestCase): + fixtures = ["timeline_tests.json"] def test_fixture(self): """Test the loaded fixture data @@ -355,7 +350,7 @@ def test_gettimelinedata(self): "Wrong data returned: ") -class CodespeedSettings(TestCase): +class TestCodespeedSettings(TestCase): """Test codespeed.settings """ @@ -418,8 +413,7 @@ def test_get_baseline_executables(self): self.assertEqual(len(result), 3) -class ProjectTest(TestCase): - """Test project model""" +class TestProject(TestCase): def setUp(self): self.github_project = Project(repo_type='H', From 626d9e1e393a0ffd10ebec23d7a7aea68a630981 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 17 Nov 2013 21:29:40 +0100 Subject: [PATCH 009/226] Add setup.py classifiers, keywords and download_url --- setup.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8628e539..498032be 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,28 @@ from setuptools import setup, find_packages setup( - author='Miquel Torres', - author_email='tobami@gmail.com', name='codespeed', version='0.9.1', + author='Miquel Torres', + author_email='tobami@gmail.com', url='https://github.com/tobami/codespeed', + download_url="https://github.com/tobami/codespeed/tags", license='GNU Lesser General Public License version 2.1', + keywords=["benchmarking", "visualization"], install_requires=['django>=1.4', 'isodate', 'south<=2.0'], packages=find_packages(exclude=['ez_setup', 'sample_project']), description='A web application to monitor and analyze the performance of your code', include_package_data=True, zip_safe=False, + classifiers =[ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP', + ] ) From ed57ec7acb14103be83c08aaadc7804f2c0d789f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 17 Nov 2013 21:30:06 +0100 Subject: [PATCH 010/226] Add Python 2.6, Django 1.3 to travis configuration --- .gitignore | 2 ++ .travis.yml | 7 ++++--- sample_project/templates/base.html | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c1c2ad32..b359fbcc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.db sample_project/repos/* override +build +dist diff --git a/.travis.yml b/.travis.yml index 0bb30f4c..cd79a159 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,17 @@ language: python python: # - "2.5" -# - "2.6" + - "2.6" - "2.7" # - "3.2" # - "3.3" # - "pypy" env: - - DJANGO_VERSION=1.4.4 - - DJANGO_VERSION=1.5.1 - DJANGO_VERSION=1.6 + - DJANGO_VERSION=1.5.5 + - DJANGO_VERSION=1.4.9 + - DJANGO_VERSION=1.3.7 # command to install dependencies install: diff --git a/sample_project/templates/base.html b/sample_project/templates/base.html index 002f1e0f..3e4c3a09 100644 --- a/sample_project/templates/base.html +++ b/sample_project/templates/base.html @@ -4,7 +4,7 @@ {% block title %}MyProject's Speed Center{% endblock %} - + {% block extra_head %}{% endblock %} From fb23f8fa9bf78623bd1e15e710f3000b751c694f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:02:50 +0100 Subject: [PATCH 011/226] Don't use deprecated urls.defaults import --- .travis.yml | 1 - sample_project/urls.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd79a159..4da65821 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ env: - DJANGO_VERSION=1.6 - DJANGO_VERSION=1.5.5 - DJANGO_VERSION=1.4.9 - - DJANGO_VERSION=1.3.7 # command to install dependencies install: diff --git a/sample_project/urls.py b/sample_project/urls.py index 6a5ddce3..f57659cc 100644 --- a/sample_project/urls.py +++ b/sample_project/urls.py @@ -3,7 +3,7 @@ import os.path from django.conf import settings -from django.conf.urls.defaults import patterns, include, handler404, handler500 +from django.conf.urls import patterns, include, handler404, handler500 from django.views.generic import RedirectView from django.core.urlresolvers import reverse from django.contrib import admin From 64df48ca149655fc376d6cb8d23437f4e3f8074f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:22:46 +0100 Subject: [PATCH 012/226] New style manage.py --- manage.py | 9 ++++++++ sample_project/manage.py | 11 --------- sample_project/settings.py | 7 +----- tools/create_apikey.py | 46 -------------------------------------- 4 files changed, 10 insertions(+), 63 deletions(-) create mode 100755 manage.py delete mode 100755 sample_project/manage.py delete mode 100755 tools/create_apikey.py diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..1c3713d6 --- /dev/null +++ b/manage.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import os, sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/sample_project/manage.py b/sample_project/manage.py deleted file mode 100755 index 5e78ea97..00000000 --- a/sample_project/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/sample_project/settings.py b/sample_project/settings.py index 02228c25..971101c8 100644 --- a/sample_project/settings.py +++ b/sample_project/settings.py @@ -64,11 +64,6 @@ # 'django.template.loaders.eggs.load_template_source', ) -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - # 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', -) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -88,7 +83,7 @@ def process_exception(self, request, exception): (request.build_absolute_uri(), traceback.format_exc())) # And add it to the middleware classes - MIDDLEWARE_CLASSES += ('settings.LogUncatchedErrors',) + MIDDLEWARE_CLASSES += ('sample_project.settings.LogUncatchedErrors',) # set shown level of logging output to debug logging.basicConfig(level=logging.DEBUG) diff --git a/tools/create_apikey.py b/tools/create_apikey.py deleted file mode 100755 index ef48548d..00000000 --- a/tools/create_apikey.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Create an API key for all users that are active""" -import sys -import os - - -## Setup to import models from Django app ## -def import_from_string(name): - """helper to import module from a given string""" - components = name.split('.')[1:] - return reduce(lambda mod, y: getattr(mod, y), components, __import__(name)) - - -sys.path.append(os.path.abspath('..')) - -if 'DJANGO_SETTINGS_MODULE' in os.environ: - settings = import_from_string(os.environ['DJANGO_SETTINGS_MODULE']) -else: - try: - import settings # Assumed to be in the same directory. - except ImportError: - import sys - sys.stderr.write( - "Error: Can't find the file 'settings.py' in the directory " - "containing %r. It appears you've customized things.\nYou'll have " - "to run django-admin.py, passing it your settings module.\n(If the" - " file settings.py does indeed exist, it's causing an ImportError " - "somehow.)\n" % __file__) - sys.exit(1) - - -from django.core.management import setup_environ -setup_environ(settings) - -from django.contrib.auth.models import User -from tastypie.models import ApiKey - - -def main(): - for user in User.objects.all(is_active=True): - ApiKey.objects.create(user=user) - - -if __name__ == '__main__': - main() From f48394d97de7c2dbc2d6f08eb86976a72b6431ed Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:34:02 +0100 Subject: [PATCH 013/226] Remove another urls.defaults import --- codespeed/urls.py | 2 +- sample_project/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codespeed/urls.py b/codespeed/urls.py index 38ab4e53..4a00204c 100644 --- a/codespeed/urls.py +++ b/codespeed/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import * +from django.conf.urls import patterns, include, url from django.core.urlresolvers import reverse from django.views.generic import TemplateView diff --git a/sample_project/urls.py b/sample_project/urls.py index f57659cc..ce9145e4 100644 --- a/sample_project/urls.py +++ b/sample_project/urls.py @@ -3,7 +3,7 @@ import os.path from django.conf import settings -from django.conf.urls import patterns, include, handler404, handler500 +from django.conf.urls import patterns, include from django.views.generic import RedirectView from django.core.urlresolvers import reverse from django.contrib import admin From 4b77963c901464307716645c377662348c9cc87d Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:37:53 +0100 Subject: [PATCH 014/226] Remove deprecated testcases.DocTestRunner --- .travis.yml | 1 - codespeed/tests/__init__.py | 8 -------- 2 files changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4da65821..131e9524 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,5 +20,4 @@ install: # command to run tests script: - python setup.py test - - cd sample_project - python manage.py test codespeed diff --git a/codespeed/tests/__init__.py b/codespeed/tests/__init__.py index 2eba701d..7fbaa939 100644 --- a/codespeed/tests/__init__.py +++ b/codespeed/tests/__init__.py @@ -4,9 +4,6 @@ import unittest from django.utils.importlib import import_module -from django.test._doctest import DocTestSuite -from django.test.testcases import DocTestRunner -from django.test.simple import doctestOutputChecker loadTestsFromModule = unittest.defaultTestLoader.loadTestsFromModule @@ -68,11 +65,6 @@ def get_suite(*names, **kwargs): suite.addTest(module.suite()) else: # otherwise build the test suite ourselves. suite.addTest(loadTestsFromModule(module)) - try: - suite.addTest(DocTestSuite(module, runner=DocTestRunner, - checker=doctestOutputChecker)) - except ValueError: # No doc tests - pass return suite suite = lambda: get_suite(__name__) From e2e7c7310f2e532ba612514bfc65efb1cc0b35f4 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:42:43 +0100 Subject: [PATCH 015/226] Update README to new manage.py location at the root directory --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c62d0fe4..9d3aa933 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ can take a long time. Please be patient. [github.com/tobami/codespeed/tags](https://github.com/tobami/codespeed/tags), unpack it and install it with `python setup.py install`. * To get started, you can use the `sample_project` directory as a starting point for your Django project, which can be normally configured by editing `sample_project/settings.py`. * For simplicity, you can use the default sqlite configuration, which will save - the data to a database named `sample_project/data.db` -* Create the DB by changing to the `sample_project/` directory and running: + the data to a database named `data.db` +* Create the DB by typing from the root directory: python manage.py syncdb @@ -61,9 +61,9 @@ modify `sample_project/settings.py` and set `DEBUG = False`. If you want to test drive Codespeed, you can use the testdata.json fixtures to have a working data set to browse. -* From the `sample_project/` directory, type: +* From the root directory, type: - ./manage.py loaddata ../codespeed/fixtures/testdata.json + ./manage.py loaddata codespeed/fixtures/testdata.json ## Starting from scratch From a26cac44dd6f3661850cc573eb5025ee353534a1 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:45:56 +0100 Subject: [PATCH 016/226] Actually remove the api.py module --- codespeed/api.py | 478 ----------------------------------------------- 1 file changed, 478 deletions(-) delete mode 100644 codespeed/api.py diff --git a/codespeed/api.py b/codespeed/api.py deleted file mode 100644 index 0fd09b2a..00000000 --- a/codespeed/api.py +++ /dev/null @@ -1,478 +0,0 @@ -# -*- coding: utf-8 -*- - -"""RESTful API implementation - -Example: -GET Environment() data: - curl -H "Accept: application/json" \ - http://127.0.0.1:8000/api/v1/environment/1/ - -POST Environment() data: - curl --dump-header - -H "Content-Type: application/json" -X POST \ - --data '{"name": "Single Core"}' \ - http://127.0.0.1:8000/api/v1/environment/ - -PUT Environment() data: - curl --dump-header - -H "Content-Type: application/json" -X PUT \ - --data '{"name": "Quad Core"}' \ - http://127.0.0.1:8000/api/v1/environment/2/ - -DELETE Environment() data: - curl --dump-header - -H "Content-Type: application/json" -X DELETE \ - http://127.0.0.1:8000/api/v1/environment/2/ - -PUT a full result: - curl --dump-header - -H "Content-Type: application/json" -X POST \ - --data '{"commitid": "4", "branch": "default", \ - "project": "MyProject", "executable": "myexe O3 64bits", \ - "benchmark": "float", "environment": "Quad Core", \ - "result_value": 4000, "result": "4000"}' \ - http://127.0.0.1:8000/api/v1/benchmark-result/ - -See http://django-tastypie.readthedocs.org/en/latest/interacting.html -""" -import logging -from datetime import datetime - -from django.contrib.auth.models import User -from django.db import models -from django.http import Http404 -from django.shortcuts import get_object_or_404 -from tastypie.bundle import Bundle -from tastypie.exceptions import ImmediateHttpResponse -from tastypie.http import HttpBadRequest, HttpCreated, HttpNotImplemented -from tastypie.resources import ModelResource, Resource -from tastypie import fields -from tastypie.authorization import Authorization, DjangoAuthorization -from tastypie.authentication import (Authentication, ApiKeyAuthentication, - MultiAuthentication) -from tastypie.models import create_api_key -from tastypie.utils.dict import dict_strip_unicode_keys - -from codespeed.models import (Environment, Project, Result, Branch, Revision, - Executable, Benchmark, Report) - - -models.signals.post_save.connect(create_api_key, sender=User) - - -class UserResource(ModelResource): - """Resource for Django User()""" - - class Meta: - queryset = User.objects.filter(is_active=True) - resource_name = 'user' - fields = ['username', 'first_name', 'last_name', 'email'] - allowed_methods = ['get'] - #excludes = ['email', 'password', 'is_superuser'] - # Add it here. - authorization = DjangoAuthorization() - authentication = ApiKeyAuthentication() - - -class ProjectResource(ModelResource): - """Resource for Project()""" - - class Meta: - queryset = Project.objects.all() - authorization = DjangoAuthorization() - # Note, the order for MultiAuthentication matters! - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class BranchResource(ModelResource): - """Resource for Branch()""" - - project = fields.ToOneField(ProjectResource, 'project') - - class Meta: - queryset = Branch.objects.all() - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class RevisionResource(ModelResource): - """Resource for Revision()""" - - project = fields.ToOneField(ProjectResource, 'project') - branch = fields.ToOneField(BranchResource, 'branch') - - class Meta: - queryset = Revision.objects.all() - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class ExecutableResource(ModelResource): - """Resource for Executable()""" - - project = fields.ToOneField(ProjectResource, 'project') - - class Meta: - queryset = Executable.objects.all() - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class BenchmarkResource(ModelResource): - """Resource for Benchmark()""" - - class Meta: - queryset = Benchmark.objects.all() - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class EnvironmentResource(ModelResource): - """Resource for Enviroment()""" - - class Meta: - queryset = Environment.objects.all() - resource_name = 'environment' - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class ResultResource(ModelResource): - """Resource for Result()""" - revision = fields.ToOneField(RevisionResource, 'revision') - executable = fields.ToOneField(ExecutableResource, 'executable') - benchmark = fields.ToOneField(BenchmarkResource, 'benchmark') - environment = fields.ToOneField(EnvironmentResource, 'environment') - - class Meta: - queryset = Result.objects.all() - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class ReportResource(ModelResource): - """Resource for Report()""" - - revision = fields.ToOneField(RevisionResource, 'revision') - environment = fields.ToOneField(EnvironmentResource, 'environment') - executable = fields.ToOneField(ExecutableResource, 'executable') - - class Meta: - queryset = Report.objects.all() - allowed_methods = ['get'] - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - - -class ResultBundle(Bundle): - """tastypie.api.Bundle class to deal with submitted results. - - Note, to populate the Bundle.obj with data .save() - has to be called first. - - FIXME (a8): add models.Data if they do not exist in DB - """ - DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' - - # order of mandatory_keys must not be changed - # FIXME (a8): refactor result_value to just value - mandatory_keys = ( - 'revision', - 'project', - 'executable', - 'benchmark', - 'environment', - 'branch', - 'result_value', - ) - - # Note, views.add_result() expects result_date. Here it's the - # same as the Result() attribute - optional_keys = ( - 'std_dev', - 'val_min', - 'val_max', - 'date', - ) - - def __init__(self, obj=None, request=None, **kwargs): - self.data = kwargs - self.request = request - - if isinstance(obj, Result): - self.obj = obj - self._populate_by_obj() - elif obj is None: - self.obj = Result() - else: - raise ValueError("obj has to be an instance of models.Result") - - if self.data: - self._check_data() - self.__data_validated = False # not used for now - super(ResultBundle, self).__init__(data=self.data, obj=self.obj, - request=self.request) - - def _populate_obj_by_data(self): - """Database lookup - - get everything except the result, 2nd try reverse lookup - """ - def populate(key): - return {'project': lambda: ProjectResource().get_via_uri( - self.data['project'], request=self.request), - - 'executable': lambda: ExecutableResource().get_via_uri( - self.data['executable'], request=self.request), - - 'benchmark': lambda: BenchmarkResource().get_via_uri( - self.data['benchmark'], request=self.request), - - 'environment': lambda: EnvironmentResource().get_via_uri( - self.data['environment'], request=self.request), - - 'branch': lambda: BranchResource().get_via_uri( - self.data['branch'], request=self.request), - - 'revision': lambda: RevisionResource().get_via_uri( - self.data['commitid'], - request=self.request)}.get(key, None)() - - try: - self.obj.value = float(self.data['result_value']) - except ValueError, error: - logging.error( - "Result value: {0} cannot be converted to float. {1}".format( - self.data['result_value'], error - )) - raise ImmediateHttpResponse( - response=HttpBadRequest(u"Value needs to be a number")) - for key in [k for k in self.mandatory_keys - if k not in ('result_value',)]: - try: - #populate - item = populate(key) - setattr(self.obj, key, item) - except Exception, error: - logging.error("Data for field %s: %s not found. %s" % ( - key, self.data[key], error)) - raise ImmediateHttpResponse( - response=HttpBadRequest(u"Error finding: {0}={1}".format( - key, self.data[key] - ))) - # populate optional data - for key in [k for k in self.optional_keys - if k not in ('date')]: - if key in self.data.keys(): - setattr(self.obj, key, self.data[key]) - - if 'date' in self.data.keys(): - self.obj.date = self.data['date'] - else: - self.obj.date = datetime.now() - - def _populate_by_obj(self): - """set attributes to make obj match ResultBundleResource - - FIXME: That looks very wrong here. - """ - self.obj.project = self.obj.executable.project - self.obj.branch = self.obj.revision.branch - #self.obj.result = self.obj - setattr(self.obj, 'result', self.obj) - - def _check_data(self): - """See if all mandatory data is there""" - # check if all mandatory keys are there - for key in [k for k in self.mandatory_keys - if k not in ('revision')]: - if not key in self.data.keys(): - error_text = u"You need to provide key: {0}".format(key) - logging.error(error_text) - raise ImmediateHttpResponse( - response=HttpBadRequest(error_text)) - # check for data - elif not self.data[key]: - error_text = 'Value for key {0} is empty.'.format(key) - logging.error(error_text) - raise ImmediateHttpResponse( - response=HttpBadRequest(error_text)) - - # Check that the Environment exists - try: - self.obj.environment = EnvironmentResource().get_via_uri( - self.data['environment'], request=self.request) - except Environment.DoesNotExist: - error_text = 'Environment: {0} not found in database.'.format( - self.data['environment']) - logging.error(error_text) - raise ImmediateHttpResponse( - response=HttpBadRequest( - error_text - )) - except Exception as e: - error_text = ('Error while looking up Environment: ' - '{0}, {1}.'.format(self.data['environment'], e)) - logging.error(error_text) - raise ImmediateHttpResponse(response=HttpBadRequest(error_text)) - # check optional data - for key in [k for k in self.optional_keys - if k not in ('date',)]: - if key in self.data.keys(): - try: - self.data[key] = float(self.data[key]) - except ValueError: - error_text = u"{0} cannot be casted to float.".format( - self.data[key]) - logging.error(error_text) - raise ImmediateHttpResponse( - response=HttpBadRequest(error_text)) - - if 'date' in self.data.keys(): - #FIXME (a8): make that more robust for different json date formats - try: - self.data['date'] = datetime.strptime(self.data['date'], - self.DATETIME_FORMAT) - except ValueError: - error_text = u"Cannot convert date {0} into datetime.".format( - self.data['date']) - logging.error(error_text) - raise ImmediateHttpResponse( - response=HttpBadRequest(error_text)) - - def hydrate_and_save(self): - """Save self.obj which is an instance of Result() - - First populate the Result() instance with self.data - """ - self._populate_obj_by_data() - self.obj.save() - - -class ResultBundleResource(Resource): - """Resource for all the data of a benchmark result. - - Primarily used to submit benchmark results - - mandatory data - 'commitid', - 'branch', - 'project', - 'executable', - 'benchmark', - 'environment', - 'result_value', - - not mandatory data - 'notify' - Send notification to registered user if result varies - from previous results, currently not implemented - """ - - revision = fields.ToOneField(RevisionResource, 'revision') - branch = fields.ToOneField(BranchResource, 'branch') - project = fields.ToOneField(ProjectResource, 'project') - executable = fields.ToOneField(ExecutableResource, 'executable') - benchmark = fields.ToOneField(BenchmarkResource, 'benchmark') - environment = fields.ToOneField(EnvironmentResource, 'environment') - result = fields.ToOneField(ResultResource, 'result') - - class Meta: - resource_name = 'benchmark-result' - object_class = Result - authorization = DjangoAuthorization() - authentication = MultiAuthentication(ApiKeyAuthentication(), - Authentication()) - allowed_methods = ['get', 'post', 'put', 'delete'] - - def get_resource_uri(self, bundle_or_obj): - kwargs = { - 'resource_name': self._meta.resource_name, - } - - if isinstance(bundle_or_obj, Bundle): - kwargs['pk'] = bundle_or_obj.obj.pk - else: - kwargs['pk'] = bundle_or_obj.pk - - if self._meta.api_name is not None: - kwargs['api_name'] = self._meta.api_name - - return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs) - - def get_object_list(self, request): - results = Result.objects.all() - - return [ResultBundle(obj=r, request=request).obj for r in results] - - def obj_get_list(self, request=None, **kwargs): - """Return all benchmark results ever""" - return self.get_object_list(request) - - def obj_get(self, request=None, **kwargs): - """get the ResultBundle with the result_id as the primary key""" - pk = kwargs['pk'] - result = Result.objects.get(pk=pk) - result.project = result.executable.project - result.branch = result.revision.branch - setattr(result, 'result', result) - return result - - def obj_create(self, bundle, request=None, **kwargs): - # not calling hydrate here since bundle.save() has that functionality - # self.full_hydrate(bundle) will try to hydrate result which is not - # there yet - #bundle = self.full_hydrate(bundle) - bundle.hydrate_and_save() - return bundle - - def obj_update(self, bundle, request=None, **kwargs): - return self.obj_create(bundle, request, **kwargs) - - def post_list(self, request, **kwargs): - """ - Creates a new resource/object with the provided data. - - Calls ``obj_create`` with the provided data and returns a response - with the new resource's location. - - If a new resource is created, return ``HttpCreated`` (201 Created). - """ - deserialized = self.deserialize( - request, request.raw_post_data, - format=request.META.get('CONTENT_TYPE', 'application/json') - ) - deserialized = self.alter_deserialized_list_data(request, deserialized) - bundle = ResultBundle(request=request, - **dict_strip_unicode_keys(deserialized)) - self.is_valid(bundle) - updated_bundle = self.obj_create(bundle, request=request) - return HttpCreated(location=self.get_resource_uri(updated_bundle)) - - def post_detail(self, request, **kwargs): - """ - Creates a new subcollection of the resource under a resource. - - This is not implemented by default because most people's data models - aren't self-referential. - - If a new resource is created, return ``HttpCreated`` (201 Created). - """ - return HttpNotImplemented() - - def obj_delete_list(self, request=None, **kwargs): - return HttpNotImplemented() - - def obj_delete(self, request=None, **kwargs): - #obj = Result.objects.get(pk=kwargs['pk']) - #obj.delete() - return HttpNotImplemented() - - def rollback(self, bundles): - pass - - def detail_uri_kwargs(self): - pass From 231033dcb772dd93ef37b4e240c45f5eefdcca2d Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 19 Nov 2013 22:48:57 +0100 Subject: [PATCH 017/226] Update installation procedure --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9d3aa933..2a6c63d1 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,15 @@ For an overview of some application concepts see the [wiki page](https://github. # Requirements -You will need Python 2.6+ and Django 1.3+ with South isodate and Tastypie. +You will need Python 2.6+ and Django 1.4+ with South and isodate -In Debian and Ubuntu, they can be installed with: +To install all needed dependencies using pip: - sudo apt-get install python-django python-django-south python-tastypie + pip install -r requirements.txt -Instead of using distribution packages, you can use pip: +To install dependencies and the codespeed Django app: - sudo pip install -r requirements.txt + python setup.py install If you want version control integration, there are additional requirements: From 2713832d6b99333d06f1fa629de7653fa4eb50db Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 20 Nov 2013 09:24:08 -0800 Subject: [PATCH 018/226] Change date formatting from just str() to isodate(), for better browser compatibility --- codespeed/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/views.py b/codespeed/views.py index 98ad7345..071c0f2a 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -437,7 +437,7 @@ def gettimelinedata(request): std_dev = res.std_dev results.append( [ - str(res.revision.date), res.value, std_dev, + res.revision.date.isoformat(), res.value, std_dev, res.revision.get_short_commitid(), branch ] ) From 6c5e3c8c275f294db080f7ae3bb28dfc4819eb02 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 23 Nov 2013 20:46:38 +0100 Subject: [PATCH 019/226] Don't use deprecated django.utils json module. Use stdlib instead --- codespeed/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/models.py b/codespeed/models.py index e41f5d2e..f879d0bf 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import os +import json from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models -from django.utils import simplejson as json from django.conf import settings from codespeed.github import GITHUB_URL_RE From 787b9a21db1c6b5c5fdf884ad344300e2d537462 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 23 Nov 2013 20:54:17 +0100 Subject: [PATCH 020/226] Use stdlib json --- codespeed/github.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/codespeed/github.py b/codespeed/github.py index 35ac538e..10444f29 100644 --- a/codespeed/github.py +++ b/codespeed/github.py @@ -8,14 +8,11 @@ import logging import urllib import re -import isodate +import json +import isodate from django.core.cache import cache -# Import from here on the off-chance someone is using a really old Python: -from django.utils import simplejson as json - - logger = logging.getLogger(__name__) GITHUB_URL_RE = re.compile( From 977a54c23ed8495e2c61d656286e1e96c65cd22f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 23 Nov 2013 21:01:31 +0100 Subject: [PATCH 021/226] Fix date format in test_gettimelinedata --- codespeed/tests/tests.py | 2 +- codespeed/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codespeed/tests/tests.py b/codespeed/tests/tests.py index bb584d35..c670f187 100644 --- a/codespeed/tests/tests.py +++ b/codespeed/tests/tests.py @@ -346,7 +346,7 @@ def test_gettimelinedata(self): "There are 2 datapoints") self.assertEquals( responsedata['timelines'][0]['branches']['default']['1'][1], - [u'2011-04-13 17:04:22', 2000.0, 1.11111, u'2', u'default'], + [u'2011-04-13T17:04:22', 2000.0, 1.11111, u'2', u'default'], "Wrong data returned: ") diff --git a/codespeed/urls.py b/codespeed/urls.py index 4a00204c..95b64925 100644 --- a/codespeed/urls.py +++ b/codespeed/urls.py @@ -28,5 +28,5 @@ urlpatterns += patterns('codespeed.views', # URLs for adding results (r'^result/add/json/$', 'add_json_results'), - (r'^result/add/$', 'add_result'), + (r'^result/add/$', 'add_result'), ) From aea70a22910d1d33c985c1c1d6b8c00bab500257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20MATHIEU?= Date: Sun, 8 Dec 2013 17:56:59 +0100 Subject: [PATCH 022/226] Better SVN error message. Error messages reported by Pysvn are usually quite good. They easily allow to understand the issue and then fix it. Codespeed swallows this nice error message to replace it by a generic one hiding what was wrong. For example is anonymous users are not allowed, you will never be able to figure it out with the "Could not resolve" message. It seems better to expose the reported error as is rather than trying to "improve" it. --- codespeed/subversion.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codespeed/subversion.py b/codespeed/subversion.py index 86fdadc8..adbb1574 100644 --- a/codespeed/subversion.py +++ b/codespeed/subversion.py @@ -33,9 +33,8 @@ def get_login(realm, username, may_save): pysvn.opt_revision_kind.number, newrev.commitid ) ) - except pysvn.ClientError: - raise RuntimeError( - "Could not resolve '" + newrev.branch.project.repo_path + "'") + except pysvn.ClientError as e: + raise RuntimeError(e.args) except ValueError: raise RuntimeError( "'%s' is an invalid subversion revision number" % newrev.commitid) From 8ae7a931be731c499c595a5bb1886bd5dc7e99b1 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Thu, 26 Dec 2013 21:37:17 +0100 Subject: [PATCH 023/226] Use branch default name from settings --- codespeed/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/models.py b/codespeed/models.py index f879d0bf..f6a7e1b4 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -98,7 +98,7 @@ def __unicode__(self): else: date = self.date.strftime("%h %d, %H:%M") string = " - ".join(filter(None, (date, self.commitid, self.tag))) - if self.branch.name != "default": + if self.branch.name != settings.DEF_BRANCH: string += " - " + self.branch.name return string From 4c1802eaa7385c6bc21d2aa4ed4aa953e11c10b3 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Thu, 26 Dec 2013 21:38:36 +0100 Subject: [PATCH 024/226] Update README: templates inherit from base.html, fixes #165 --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2a6c63d1..c66f159e 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,7 @@ values needed for your customizations: ### Site-wide Changes -All pages inherit from the `site_base.html` template, which -extends `base.html`. To change every page on the site simply edit (`sample_project/templates/site_base.html`) which extends `base.html` and override +All pages inherit from the `base.html` template. To change every page on the site simply edit (`sample_project/templates/base.html`) and override the appropriate block: * Custom title: you may replace the default "My Speed Center" for the title @@ -152,7 +151,7 @@ same name. * About page: create `sample_project/override/templates/about.html`: - {% extends "site_base.html" %} + {% extends "base.html" %} {% block title %}{{ block.super }}: About this project{% endblock %} {% block body %} From fd5c74e71a27fa6fa6e93da8958a14ac27898906 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Fri, 27 Dec 2013 16:30:29 +0100 Subject: [PATCH 025/226] Use %b instead of %h for datetime formatting. Fixes #163 --- codespeed/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/models.py b/codespeed/models.py index f6a7e1b4..edacb205 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -96,7 +96,7 @@ def __unicode__(self): if self.date is None: date = None else: - date = self.date.strftime("%h %d, %H:%M") + date = self.date.strftime("%b %d, %H:%M") string = " - ".join(filter(None, (date, self.commitid, self.tag))) if self.branch.name != settings.DEF_BRANCH: string += " - " + self.branch.name From 66315feb8a514259f322ca35db4c8d391b3e7aac Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Fri, 27 Dec 2013 17:11:22 +0100 Subject: [PATCH 026/226] Release 0.10.0 --- .travis.yml | 1 - CHANGELOG | 13 +++++++++++++ setup.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 131e9524..9be93cf2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: -# - "2.5" - "2.6" - "2.7" # - "3.2" diff --git a/CHANGELOG b/CHANGELOG index e8dbb40f..cd91f7e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,18 @@ == Change Log == +=== Version 0.10.0, December 27, 2013 === +* NEW #108: AlekSi made executable names uniques per project instead of globally +* NEW #148: staticfloat added the ability to set benchmark fields when saving results for a new benchmark +* NEW #149, #161: rchikhi and others fixed Django compatibility with versions 1.4-1.6 +* NEW #158: smarr added initial travis configuration. Continuous integration is now enabled for Codespeed +* NEW #32: squiddy added option to control display of authors email address +* OPTIMIZATION #98: squiddy greatly reduced the number of necessary queries for getting comparison data, getting down the response time by an order of magnitude +* FIX #86: a8 fixed timeline plot failing when more than 4 series were selected +* FIX #130: alkino fixed migrations 0006 and 0012 +* FIX #151: philangist migrated Github integration to API v3 +* FIX #85: a8 fixed timeline view on IE + + === Version 0.9.1, September 11, 2011 === * NEW: Stefan added the possibility to name the default branch other than "default" * NEW: Danilo added executable grouping by project diff --git a/setup.py b/setup.py index 498032be..a19b896c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='codespeed', - version='0.9.1', + version='0.10.0', author='Miquel Torres', author_email='tobami@gmail.com', url='https://github.com/tobami/codespeed', From 5b6b7b793f07128d0f88bb1ed6e5a2f128558ec4 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 4 Jan 2014 11:33:01 +0100 Subject: [PATCH 027/226] Update classifiers, copyright --- LICENSE | 6 +++--- setup.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index b0e0b3c4..a1b3d0b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ All files in this work, are now covered by the following copyright notice. Please note that included libraries in the media/ directory may have their own license. - -Copyright (c) 2009-2010 Miquel Torres - + +Copyright (c) 2009-2014 Miquel Torres + This file is part of Codespeed. Codespeed is free software; you can redistribute it and/or diff --git a/setup.py b/setup.py index a19b896c..49bae9fa 100644 --- a/setup.py +++ b/setup.py @@ -21,8 +21,9 @@ 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', ] ) From 9c444087cfcee0d828a8e485938de6ad89723b29 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 4 Jan 2014 11:33:11 +0100 Subject: [PATCH 028/226] Remove unused site_base.html --- sample_project/templates/site_base.html | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 sample_project/templates/site_base.html diff --git a/sample_project/templates/site_base.html b/sample_project/templates/site_base.html deleted file mode 100644 index 90455eee..00000000 --- a/sample_project/templates/site_base.html +++ /dev/null @@ -1,3 +0,0 @@ -{% extends "base.html" %} - -{# This exists only to be overriden #} \ No newline at end of file From e6e1986063082fc43772c6ff657bd7ac75678792 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 4 Jan 2014 11:56:05 +0100 Subject: [PATCH 029/226] Clarify installation --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c66f159e..9ac1dd2e 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,13 @@ Known to be used by [PyPy](http://speed.pypy.org), [Twisted](http://speed.twiste For an overview of some application concepts see the [wiki page](https://github.com/tobami/codespeed/wiki/Overview) -# Requirements - -You will need Python 2.6+ and Django 1.4+ with South and isodate - -To install all needed dependencies using pip: +# Installation - pip install -r requirements.txt +You will need Python 2.6 or 2.7. To install dependencies and the codespeed Django app: - python setup.py install + pip install codespeed If you want version control integration, there are additional requirements: @@ -30,8 +26,6 @@ If you want version control integration, there are additional requirements: Codespeed will try to clone the repo, which depending on the size of the project can take a long time. Please be patient. -# Installation - * Download the last stable release from [github.com/tobami/codespeed/tags](https://github.com/tobami/codespeed/tags), unpack it and install it with `python setup.py install`. * To get started, you can use the `sample_project` directory as a starting point for your Django project, which can be normally configured by editing `sample_project/settings.py`. @@ -42,7 +36,7 @@ can take a long time. Please be patient. python manage.py syncdb * Create an admin user in the process. -* Migrate to the new DB Schema: +* Execute DB migrations: python manage.py migrate @@ -54,6 +48,7 @@ The codespeed installation can now be accessed by navigating to `http://localhos **Note**: for production, you should configure a real server like Apache or nginx (refer to the [Django docs](http://docs.djangoproject.com/en/dev/howto/deployment/)). You should also modify `sample_project/settings.py` and set `DEBUG = False`. +[`sample_project/README.md`](https://github.com/tobami/codespeed/tree/master/sample_project/README.md) also describes some production settings. # Codespeed configuration From 71333b8812aae7faf523cc3e1b9f27e359f203f1 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 4 Jan 2014 11:57:31 +0100 Subject: [PATCH 030/226] Better explain sample_project installation. Partly addresses #166 --- sample_project/README.md | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/sample_project/README.md b/sample_project/README.md index e0a6e1fb..5a98d484 100644 --- a/sample_project/README.md +++ b/sample_project/README.md @@ -4,8 +4,6 @@ Codespeed uses the Web framework [Django](http://djangoproject.com/). To get a Codespeed instance running you need to set up a Django Project. This directory is just such a project for your reference and a jump start to create your own. -This file is written in Markdown. - ## For the impatient Warning: It is recommended to use [virtualenv](http://pypi.python.org/pypi/virtualenv) to avoid installing @@ -15,35 +13,33 @@ environments. ### Testing with the built-in Development Server That will give you *just* the Django development server version. Please -refer to *Installing for Production* for serious installations. You have been warned! +refer to *Installing for Production* for serious installations. It is assumed you are in the root directory of the Codespeed software. 1. Install the Python pip module `which pip >/dev/null || easy_install pip` (You might be required to use sudo) -2. You *must* copy the example directory to your project. (Prevents updates on +2. You *must* copy the `sample_project` directory to your project. (Prevents updates on git tracked files in the future.) Let's call it speedcenter - `cp -r example speedcenter` -3. Enter that directory - `cd speedcenter` -4. Install Django, Codespeed and other dependencies using pip - `pip install -r requirements.txt` - (You might be required to use sudo) -5. Add codespeed to your Python path + `cp -r sample_project speedcenter` +3a. (When configuring your own project) `pip install codespeeed` +3b. (For Codespeed development) Install Django and other dependencies using pip + `pip install -r requirements.txt`. This will not install codespeed itself, as we want runserver to only "see" the local codespeed copy +4. Add codespeed to your Python path Either `export PYTHONPATH=../:$PYTHONPATH` or - `ln -s ../codespeed .` -6. Initialise the Django Database + `ln -s ./codespeed ./sample_project` +5. Initialise the Django Database `python manage.py syncdb` (Yes, add a superuser.) `python manage.py migrate` Optionally, you may want to load the fixture data for a try `python manage.py loaddata ../codespeed/fixtures/testdata.json` -7. Finally, start the Django development server. +6. Finally, start the Django development server. `python manage.py runserver` -8. Enjoy. +7. Enjoy. `python -m webbrowser -n http://localhost:8000` ## Installing for production @@ -133,12 +129,4 @@ Please, also refer to the [Django URL dispatcher docu] ### Codespeed settings The main config file is `settings.py`. There you configure everything related -to your set up. It FIXME (a8) to be continued... - -FIXME (a8 2011-04-29): Write more ... - -* Point to Django docu for DB, template engine, ... -* Point to codespeed config in settings.py -* Point to wsgi config for Apache ... -* Write up "Installing for production" section -... +to your set up. From f400be47f37d2aeec60dc88a86a544b255d8b9cf Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 5 Jan 2014 10:41:18 +0100 Subject: [PATCH 031/226] Reintroduce base_site.html to override individual template blocks --- README.md | 11 ++++++----- .../templates/codespeed}/base.html | 0 codespeed/templates/codespeed/base_site.html | 3 +++ codespeed/templates/codespeed/changes.html | 2 +- codespeed/templates/codespeed/comparison.html | 2 +- codespeed/templates/codespeed/nodata.html | 2 +- codespeed/templates/codespeed/timeline.html | 2 +- sample_project/README.md | 6 +++--- sample_project/templates/404.html | 2 +- sample_project/templates/500.html | 2 +- sample_project/templates/about.html | 2 +- sample_project/templates/codespeed/base_site.html | 5 +++++ sample_project/templates/home.html | 2 +- 13 files changed, 25 insertions(+), 16 deletions(-) rename {sample_project/templates => codespeed/templates/codespeed}/base.html (100%) create mode 100644 codespeed/templates/codespeed/base_site.html create mode 100644 sample_project/templates/codespeed/base_site.html diff --git a/README.md b/README.md index 9ac1dd2e..1396d520 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ section). ## Custom Settings -You may override any of the default settings by creating the file -`sample_project/override/settings.py`. It is strongly recommended that you only override the +You may override any of the default settings by setting them in +`sample_project/settings.py`. It is strongly recommended that you only override the settings you need by importing the default settings and replacing only the values needed for your customizations: @@ -110,13 +110,14 @@ values needed for your customizations: ### Site-wide Changes -All pages inherit from the `base.html` template. To change every page on the site simply edit (`sample_project/templates/base.html`) and override +All pages inherit from the `base.html` template. To change every page on the site +simply edit (`sample_project/templates/codespeed/base_site.html`) and override the appropriate block: * Custom title: you may replace the default "My Speed Center" for the title block with your prefered value: - {% block title} + {% block title %} My Project's Speed Center {% endblock %} @@ -146,7 +147,7 @@ same name. * About page: create `sample_project/override/templates/about.html`: - {% extends "base.html" %} + {% extends "codespeed/base_site.html" %} {% block title %}{{ block.super }}: About this project{% endblock %} {% block body %} diff --git a/sample_project/templates/base.html b/codespeed/templates/codespeed/base.html similarity index 100% rename from sample_project/templates/base.html rename to codespeed/templates/codespeed/base.html diff --git a/codespeed/templates/codespeed/base_site.html b/codespeed/templates/codespeed/base_site.html new file mode 100644 index 00000000..87740917 --- /dev/null +++ b/codespeed/templates/codespeed/base_site.html @@ -0,0 +1,3 @@ +{% extends "codespeed/base.html" %} + +{# This exists only to be overriden #} diff --git a/codespeed/templates/codespeed/changes.html b/codespeed/templates/codespeed/changes.html index ebc09516..3d8e7f05 100644 --- a/codespeed/templates/codespeed/changes.html +++ b/codespeed/templates/codespeed/changes.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% load url from future %} {% block title %}{{ block.super }}: Changes{% endblock %} diff --git a/codespeed/templates/codespeed/comparison.html b/codespeed/templates/codespeed/comparison.html index 30d80a59..79953b8b 100644 --- a/codespeed/templates/codespeed/comparison.html +++ b/codespeed/templates/codespeed/comparison.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% load url from future %} {% block title %}{{ block.super }}: Comparison{% endblock %} diff --git a/codespeed/templates/codespeed/nodata.html b/codespeed/templates/codespeed/nodata.html index 575e4ddb..969ede42 100644 --- a/codespeed/templates/codespeed/nodata.html +++ b/codespeed/templates/codespeed/nodata.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% block body %}

{{ message|safe }}

diff --git a/codespeed/templates/codespeed/timeline.html b/codespeed/templates/codespeed/timeline.html index 393d63ba..0ac46877 100644 --- a/codespeed/templates/codespeed/timeline.html +++ b/codespeed/templates/codespeed/timeline.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% load url from future %} {% block title %}{{ block.super }}: Timeline{% endblock %} diff --git a/sample_project/README.md b/sample_project/README.md index 5a98d484..eb0c916a 100644 --- a/sample_project/README.md +++ b/sample_project/README.md @@ -110,14 +110,14 @@ Assumed you have a [Debian](http://www.debian.org) like system. ### Using your own Templates Just edit your very own Django templates in `speedcenter/templates`. A good -start is `base.html` the root of all templates. +start is `codespeed/base.html` the root of all templates. If you need to change the codespeed templates: 1. Copy the templates from the codespeed module into your Django project folder. `cp -r codespeed/templates/codespeed speedcenter/templates/` 2. Edit the templates in speedcenter/templates/codespeed/*html Please, also refer to the [Django template docu] -(http://docs.djangoproject.com/en/1.3/ref/templates/) +(http://docs.djangoproject.com/en/1.4/ref/templates/) ### Changing the URL Scheme If you don't want to have your speedcenter in the root url you can change urls.py. @@ -125,7 +125,7 @@ Comment (add a '#' at the beginning) line number 25 `(r'^', include('cod...` and uncomment the next line `(r'^speed/', include('cod...` (Note, Python is picky about indentation). Please, also refer to the [Django URL dispatcher docu] -(http://docs.djangoproject.com/en/1.3/topics/http/urls/). +(http://docs.djangoproject.com/en/1.4/topics/http/urls/). ### Codespeed settings The main config file is `settings.py`. There you configure everything related diff --git a/sample_project/templates/404.html b/sample_project/templates/404.html index 09d204a3..f627be7f 100644 --- a/sample_project/templates/404.html +++ b/sample_project/templates/404.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% block title %}Page not found{% endblock %} diff --git a/sample_project/templates/500.html b/sample_project/templates/500.html index af785c0c..567378e6 100644 --- a/sample_project/templates/500.html +++ b/sample_project/templates/500.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% block title %}Page unavailable{% endblock %} diff --git a/sample_project/templates/about.html b/sample_project/templates/about.html index 931cf62b..7eebe2e7 100644 --- a/sample_project/templates/about.html +++ b/sample_project/templates/about.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "codespeed/base_site.html" %} {% block title %}{{ block.super }}: About this site{% endblock %} {% block body %} diff --git a/codespeed/templates/codespeed/changes_table.html b/codespeed/templates/codespeed/changes_table.html index ec067f31..0b2094b4 100644 --- a/codespeed/templates/codespeed/changes_table.html +++ b/codespeed/templates/codespeed/changes_table.html @@ -1,6 +1,5 @@ {% load percentages %} -
{% for units in tablelist %} @@ -64,45 +63,3 @@
Kernel{{ env.kernel }}
-
- -
- - - - - - - - - - - - - - - {% ifnotequal rev.branch.project.repo_type "N" %} - - {% endifnotequal %} - -
Revision
Commit{% if rev.get_browsing_url %}{{ rev.commitid }}{% else %}{{ rev.commitid }}{% endif %}
Date{{ rev.date }}
Repo{{ rev.branch.project.repo_path }}
- -{% ifnotequal exe.project.repo_type 'N' %} - - - - - - - - - - - - -
Commit logs
Loading...
-{% endifnotequal %} -
diff --git a/codespeed/views.py b/codespeed/views.py index 66b3cc20..70ff2f50 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -611,7 +611,7 @@ def getchangestable(request): '

No results for this ' 'parameters

') - return render_to_response('codespeed/changes_table.html', { + return render_to_response('codespeed/changes_data.html', { 'tablelist': tablelist, 'trendconfig': trendconfig, 'rev': selectedrev, From 0d8f6aec4fbab8b5e867bd2a6d49ae7153e6bdf7 Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Sat, 2 Aug 2014 17:02:59 +0200 Subject: [PATCH 044/226] New color "light green" for positive trends to distinguish them from non-trend improvements. --- codespeed/models.py | 14 ++++++++++---- codespeed/static/css/main.css | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/codespeed/models.py b/codespeed/models.py index 58a68f77..13b5f0e3 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -283,8 +283,11 @@ def save(self, *args, **kwargs): self.summary = "Average %s trend %s" % ( average_trend_units.lower(), self.updown(average_trend)) - self.colorcode = average_trend_color == "red"\ - and "yellow" or average_trend_color + # use lighter colors for trend results: + if average_trend_color == "red": + self.colorcode = "yellow" + elif average_trend_color == "green": + self.colorcode = "lightgreen" # Single benchmark trend if max_trend_color != "none" and self.colorcode != "red": if (self.colorcode == "none" or @@ -292,8 +295,11 @@ def save(self, *args, **kwargs): self.summary = "%s trend %s" % ( max_trend_ben, self.updown(max_trend)) - self.colorcode = max_trend_color == "red"\ - and "yellow" or max_trend_color + # use lighter colors for trend results: + if max_trend_color == "red": + self.colorcode = "yellow" + elif max_trend_color == "green": + self.colorcode = "lightgreen" super(Report, self).save(*args, **kwargs) diff --git a/codespeed/static/css/main.css b/codespeed/static/css/main.css index 3b6909a8..e3668f1a 100644 --- a/codespeed/static/css/main.css +++ b/codespeed/static/css/main.css @@ -358,6 +358,8 @@ table.tablesorter tbody tr td.status-green, td.status-green { background-color: tr.status-green td.summary { color: #9FD54D; font-weight: bold; } table.tablesorter tbody tr td.status-yellow, td.status-yellow { background-color: #FFD843; } tr.status-yellow td.summary { color: #FFD843; font-weight: bold; } +table.tablesorter tbody tr td.status-lightgreen, td.status-lightgreen { background-color: #c2e927; } +tr.status-lightgreen td.summary { color: #c2e927; font-weight: bold; } table.tablesorter tbody tr.highlight td, tr.highlight td { background-color: #9DADC6 !important; From 2538ca71fe2e5b6c9032ff7675f55132d69a2c39 Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Sat, 2 Aug 2014 17:30:13 +0200 Subject: [PATCH 045/226] Simplify classification logic for the summaries (changes results before trends, averages before individual results) --- codespeed/models.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/codespeed/models.py b/codespeed/models.py index 13b5f0e3..5b58e985 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -265,21 +265,24 @@ def save(self, *args, **kwargs): self.colorcode = "none" # Save summary in order of priority + # (changes results before trends, averages before individual results) + # Average change if average_change_color != "none": self.summary = "Average %s %s" % ( average_change_units.lower(), self.updown(average_change)) self.colorcode = average_change_color + # Single benchmark change - if max_change_color != "none" and self.colorcode != "red": + elif max_change_color != "none": self.summary = "%s %s" % ( max_change_ben, self.updown(max_change)) self.colorcode = max_change_color # Average trend - if average_trend_color != "none" and self.colorcode == "none": + elif average_trend_color != "none": self.summary = "Average %s trend %s" % ( average_trend_units.lower(), self.updown(average_trend)) @@ -288,18 +291,17 @@ def save(self, *args, **kwargs): self.colorcode = "yellow" elif average_trend_color == "green": self.colorcode = "lightgreen" + # Single benchmark trend - if max_trend_color != "none" and self.colorcode != "red": - if (self.colorcode == "none" or - (self.colorcode == "green" and "trend" not in self.summary)): - self.summary = "%s trend %s" % ( - max_trend_ben, - self.updown(max_trend)) - # use lighter colors for trend results: - if max_trend_color == "red": - self.colorcode = "yellow" - elif max_trend_color == "green": - self.colorcode = "lightgreen" + elif max_trend_color != "none": + self.summary = "%s trend %s" % ( + max_trend_ben, + self.updown(max_trend)) + # use lighter colors for trend results: + if max_trend_color == "red": + self.colorcode = "yellow" + elif max_trend_color == "green": + self.colorcode = "lightgreen" super(Report, self).save(*args, **kwargs) From 8eda93100c04fc01dbccd7c6a1425256450170e3 Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Sun, 3 Aug 2014 17:09:05 +0200 Subject: [PATCH 046/226] Integrate code review --- codespeed/feeds.py | 4 +--- codespeed/urls.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/codespeed/feeds.py b/codespeed/feeds.py index 474fdfde..345c26f2 100644 --- a/codespeed/feeds.py +++ b/codespeed/feeds.py @@ -2,8 +2,6 @@ from codespeed.models import Report from django.conf import settings from django.db.models import Q -from django.shortcuts import render_to_response - class ResultFeed(Feed): title = settings.WEBSITE_NAME @@ -40,7 +38,7 @@ def result_filter(self): return Q(revision__branch__name=settings.DEF_BRANCH) class LatestSignificantEntries(ResultFeed): - description = "Last significant benchmark runs" + description = "Last benchmark runs with significant changes" def result_filter(self): return Q(revision__branch__name=settings.DEF_BRANCH, diff --git a/codespeed/urls.py b/codespeed/urls.py index 0583331e..a6b5ed10 100644 --- a/codespeed/urls.py +++ b/codespeed/urls.py @@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse from django.views.generic import TemplateView -from codespeed.feeds import * +from codespeed.feeds import LatestEntries, LatestSignificantEntries urlpatterns = patterns('', From 471b2dc54b057e7a8eac4ec97184243bb350439a Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 3 Aug 2014 22:49:35 +0200 Subject: [PATCH 047/226] Add test for reports view --- codespeed/feeds.py | 8 ++++++-- codespeed/tests/test_views.py | 32 ++++++++++++++++++++++++++++++++ codespeed/tests/tests.py | 3 +-- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 codespeed/tests/test_views.py diff --git a/codespeed/feeds.py b/codespeed/feeds.py index 345c26f2..911fe395 100644 --- a/codespeed/feeds.py +++ b/codespeed/feeds.py @@ -3,6 +3,7 @@ from django.conf import settings from django.db.models import Q + class ResultFeed(Feed): title = settings.WEBSITE_NAME link = "/changes/" @@ -13,7 +14,8 @@ def items(self): .order_by('-revision__date')[:10] def item_title(self, item): - return "%s: %s" % (item.revision.get_short_commitid(), item.item_description()) + return "%s: %s" % (item.revision.get_short_commitid(), + item.item_description()) description_template = "codespeed/changes_table.html" @@ -31,15 +33,17 @@ def get_context_data(self, **kwargs): 'env': report.environment, } + class LatestEntries(ResultFeed): description = "Last benchmark runs" def result_filter(self): return Q(revision__branch__name=settings.DEF_BRANCH) + class LatestSignificantEntries(ResultFeed): description = "Last benchmark runs with significant changes" def result_filter(self): return Q(revision__branch__name=settings.DEF_BRANCH, - colorcode__in = ('red','green')) + colorcode__in=('red', 'green')) diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py new file mode 100644 index 00000000..8b2c861e --- /dev/null +++ b/codespeed/tests/test_views.py @@ -0,0 +1,32 @@ +from django.test import TestCase +from django.core.urlresolvers import reverse + +from codespeed.models import Environment + + +class TestAddResult(TestCase): + + def setUp(self): + self.path = reverse('codespeed.views.reports') + self.e = Environment.objects.create(name='Dual Core', cpu='Core 2 Duo 8200') + self.data = { + 'commitid': 'abcd1', + 'branch': 'default', + 'project': 'MyProject', + 'executable': 'myexe O3 64bits', + 'benchmark': 'float', + 'environment': 'Dual Core', + 'result_value': 200, + } + resp = self.client.post(reverse('codespeed.views.add_result'), self.data) + self.assertEqual(resp.status_code, 202) + self.data['commitid'] = "abcd2" + self.client.post(reverse('codespeed.views.add_result'), self.data) + assert resp.status_code == 202 + + def test_reports(self): + response = self.client.get(self.path) + + self.assertEqual(response.status_code, 200) + self.assertIn('Latest Results', response.content) + self.assertIn(self.data['commitid'], response.content) diff --git a/codespeed/tests/tests.py b/codespeed/tests/tests.py index c670f187..be411cfb 100644 --- a/codespeed/tests/tests.py +++ b/codespeed/tests/tests.py @@ -18,8 +18,7 @@ class TestAddResult(TestCase): def setUp(self): self.path = reverse('codespeed.views.add_result') - self.e = Environment(name='Dual Core', cpu='Core 2 Duo 8200') - self.e.save() + self.e = Environment.objects.create(name='Dual Core', cpu='Core 2 Duo 8200') temp = datetime.today() self.cdate = datetime( temp.year, temp.month, temp.day, temp.hour, temp.minute, temp.second) From 3b1c5dc753b64be0ab2304568558825ae922f342 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 3 Aug 2014 23:02:45 +0200 Subject: [PATCH 048/226] Use require_GET decorator to disallow non-GET requests --- codespeed/tests/test_views.py | 9 ++++++++- codespeed/views.py | 33 +++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py index 8b2c861e..9d1113ae 100644 --- a/codespeed/tests/test_views.py +++ b/codespeed/tests/test_views.py @@ -21,12 +21,19 @@ def setUp(self): resp = self.client.post(reverse('codespeed.views.add_result'), self.data) self.assertEqual(resp.status_code, 202) self.data['commitid'] = "abcd2" + self.data['result_value'] = 150 self.client.post(reverse('codespeed.views.add_result'), self.data) - assert resp.status_code == 202 + self.assertEqual(resp.status_code, 202) def test_reports(self): response = self.client.get(self.path) self.assertEqual(response.status_code, 200) self.assertIn('Latest Results', response.content) + self.assertIn('Latest Significant Results', response.content) self.assertIn(self.data['commitid'], response.content) + + def test_reports_post_returns_405(self): + response = self.client.post(self.path, {}) + + self.assertEqual(response.status_code, 405) diff --git a/codespeed/views.py b/codespeed/views.py index 70ff2f50..d3558993 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- from datetime import datetime -from itertools import chain import json import logging +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse from django.http import (HttpResponse, Http404, HttpResponseNotAllowed, HttpResponseBadRequest) from django.shortcuts import get_object_or_404, render_to_response +from django.views.decorators.http import require_GET +from django.views.decorators.csrf import csrf_exempt from django.template import RequestContext -from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse from django.conf import settings -from django.views.decorators.csrf import csrf_exempt from codespeed.models import (Environment, Report, Project, Revision, Result, Executable, Benchmark, Branch) @@ -193,11 +193,8 @@ def getcomparisonexes(): return all_executables, exekeys +@require_GET def getcomparisondata(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') - data = request.GET - executables, exekeys = getcomparisonexes() benchmarks = Benchmark.objects.all() environments = Environment.objects.all() @@ -226,9 +223,8 @@ def getcomparisondata(request): return HttpResponse(json.dumps(compdata)) +@require_GET def comparison(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') data = request.GET # Configuration of default parameters @@ -361,9 +357,8 @@ def comparison(request): }, context_instance=RequestContext(request)) +@require_GET def gettimelinedata(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') data = request.GET timeline_list = {'error': 'None', 'timelines': []} @@ -475,9 +470,8 @@ def gettimelinedata(request): return HttpResponse(json.dumps(timeline_list)) +@require_GET def timeline(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') data = request.GET ## Configuration of default parameters ## @@ -590,6 +584,7 @@ def timeline(request): }, context_instance=RequestContext(request)) +@require_GET def getchangestable(request): executable = get_object_or_404(Executable, pk=request.GET.get('exe')) environment = get_object_or_404(Environment, pk=request.GET.get('env')) @@ -620,9 +615,8 @@ def getchangestable(request): }, context_instance=RequestContext(request)) +@require_GET def changes(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') data = request.GET # Configuration of default parameters @@ -726,10 +720,8 @@ def changes(request): }, context_instance=RequestContext(request)) +@require_GET def reports(request): - if request.method != 'GET': - return HttpResponseNotAllowed('GET') - context = {} context['reports'] = \ @@ -747,6 +739,7 @@ def reports(request): context, context_instance=RequestContext(request)) +@require_GET def displaylogs(request): rev = get_object_or_404(Revision, pk=request.GET.get('revisionid')) logs = [] @@ -796,6 +789,7 @@ def displaylogs(request): context_instance=RequestContext(request)) +@require_GET def getcommitlogs(rev, startrev, update=False): logs = [] @@ -853,7 +847,6 @@ def validate_result(item): 'result_value', ] - response = {} error = True for key in mandatory_data: if not key in item: From b732cd6b9e3b13ead3e09ab63896c4d725c7b33a Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Thu, 16 Oct 2014 12:40:24 +0200 Subject: [PATCH 049/226] Remove @require_GET from getcommitlog As it is not a view. Fixes #175. --- codespeed/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/codespeed/views.py b/codespeed/views.py index d3558993..09319fe0 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -789,7 +789,6 @@ def displaylogs(request): context_instance=RequestContext(request)) -@require_GET def getcommitlogs(rev, startrev, update=False): logs = [] From 1bc316fe20dde1c3411e7062303d8f683d34996f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 4 Nov 2014 22:04:14 +0100 Subject: [PATCH 050/226] Add filters to admin models. Fixes #179 --- codespeed/admin.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/codespeed/admin.py b/codespeed/admin.py index 22878c22..f70b76b5 100644 --- a/codespeed/admin.py +++ b/codespeed/admin.py @@ -14,13 +14,14 @@ class ProjectAdmin(admin.ModelAdmin): class BranchAdmin(admin.ModelAdmin): list_display = ('name', 'project') + list_filter = ('project',) admin.site.register(Branch, BranchAdmin) class RevisionAdmin(admin.ModelAdmin): list_display = ('commitid', 'branch', 'tag', 'date') - list_filter = ('branch', 'tag', 'date') + list_filter = ('branch__project', 'branch', 'tag', 'date') search_fields = ('commitid', 'tag') admin.site.register(Revision, RevisionAdmin) @@ -28,6 +29,7 @@ class RevisionAdmin(admin.ModelAdmin): class ExecutableAdmin(admin.ModelAdmin): list_display = ('name', 'description', 'id', 'project') + list_filter = ('project',) search_fields = ('name', 'description', 'project') admin.site.register(Executable, ExecutableAdmin) @@ -36,6 +38,7 @@ class ExecutableAdmin(admin.ModelAdmin): class BenchmarkAdmin(admin.ModelAdmin): list_display = ('name', 'benchmark_type', 'description', 'units_title', 'units', 'lessisbetter', 'default_on_comparison') + list_filter = ('lessisbetter',) ordering = ['name'] search_fields = ('name', 'description') @@ -52,7 +55,7 @@ class EnvironmentAdmin(admin.ModelAdmin): class ResultAdmin(admin.ModelAdmin): list_display = ('revision', 'benchmark', 'executable', 'environment', 'value', 'date', 'environment') - list_filter = ('date', 'environment', 'executable', 'benchmark') + list_filter = ('environment', 'executable', 'date', 'benchmark') admin.site.register(Result, ResultAdmin) @@ -62,8 +65,10 @@ def recalculate_report(modeladmin, request, queryset): report.save() recalculate_report.short_description = "Recalculate reports" + class ReportAdmin(admin.ModelAdmin): list_display = ('revision', 'summary', 'colorcode') + list_filter = ('environment', 'executable') ordering = ['-revision'] actions = [recalculate_report] From 38eaa7317c620f1e314c2f6abdc41925db166d0a Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Tue, 4 Nov 2014 22:06:57 +0100 Subject: [PATCH 051/226] Add plural name for Branch model --- codespeed/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/codespeed/models.py b/codespeed/models.py index 5b58e985..51a5e06b 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -73,6 +73,7 @@ def __unicode__(self): class Meta: unique_together = ("name", "project") + verbose_name_plural = "branches" class Revision(models.Model): From 47b50a717bd5a28f8513ff9a3c492d8629cad4f6 Mon Sep 17 00:00:00 2001 From: Kevin Modzelewski Date: Wed, 5 Nov 2014 20:21:08 -0800 Subject: [PATCH 052/226] codespeed doesn't work with Django 1.7 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 821662d4..e6c44b84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Django>=1.4 +Django>=1.4,<1.7 South<=2.0 isodate==0.4.8 From b9f57e42a69e4a07443c1bc7b1237ac2b56ed356 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 22 Mar 2015 11:11:04 +0100 Subject: [PATCH 053/226] Show better error message when no project has track=True --- codespeed/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codespeed/views.py b/codespeed/views.py index 09319fe0..157bf87a 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -233,7 +233,7 @@ def comparison(request): return no_environment_error(request) checkedenviros = get_default_environment(enviros, data, multi=True) - if not len(Project.objects.all()): + if not len(Project.objects.filter(track=True)): return no_default_project_error(request) # Check whether there exist appropiate executables @@ -639,6 +639,9 @@ def changes(request): return no_environment_error(request) defaultenv = get_default_environment(enviros, data) + if not len(Project.objects.filter(track=True)): + return no_default_project_error(request) + defaultexecutable = getdefaultexecutable() if not defaultexecutable: return no_executables_error(request) From ccb422e07e56a788880ad6e95e9d7c6daf9bca95 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 22 Mar 2015 11:12:40 +0100 Subject: [PATCH 054/226] Default to track=True for new projects --- codespeed/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/models.py b/codespeed/models.py index 51a5e06b..1e17afca 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -29,7 +29,7 @@ class Project(models.Model): blank=True, max_length=100) commit_browsing_url = models.CharField("Commit browsing URL", blank=True, max_length=200) - track = models.BooleanField("Track changes", default=False) + track = models.BooleanField("Track changes", default=True) def __unicode__(self): return self.name From fa5c6aab0c5111ba3b801d2be4bed92688a3fc96 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sun, 22 Mar 2015 11:23:53 +0100 Subject: [PATCH 055/226] Fix executable admin search field. Fixes #139 --- codespeed/admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codespeed/admin.py b/codespeed/admin.py index f70b76b5..99148fb6 100644 --- a/codespeed/admin.py +++ b/codespeed/admin.py @@ -30,7 +30,8 @@ class RevisionAdmin(admin.ModelAdmin): class ExecutableAdmin(admin.ModelAdmin): list_display = ('name', 'description', 'id', 'project') list_filter = ('project',) - search_fields = ('name', 'description', 'project') + ordering = ['name'] + search_fields = ('name', 'description', 'project__name') admin.site.register(Executable, ExecutableAdmin) @@ -47,6 +48,7 @@ class BenchmarkAdmin(admin.ModelAdmin): class EnvironmentAdmin(admin.ModelAdmin): list_display = ('name', 'cpu', 'memory', 'os', 'kernel') + ordering = ['name'] search_fields = ('name', 'cpu', 'memory', 'os', 'kernel') admin.site.register(Environment, EnvironmentAdmin) From f91ab12c0a85d0aa92bea781fedebacf722b1115 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Thu, 30 Apr 2015 14:29:50 +1000 Subject: [PATCH 056/226] Correct install_requires to match requirements.txt --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 49bae9fa..a1e3b1aa 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ download_url="https://github.com/tobami/codespeed/tags", license='GNU Lesser General Public License version 2.1', keywords=["benchmarking", "visualization"], - install_requires=['django>=1.4', 'isodate', 'south<=2.0'], + install_requires=['django>=1.4,<1.7', 'isodate==0.4.8', 'south<=2.0'], packages=find_packages(exclude=['ez_setup', 'sample_project']), description='A web application to monitor and analyze the performance of your code', include_package_data=True, From 7cd9dc2db802ab6c84e39d60658db9c062cdd6da Mon Sep 17 00:00:00 2001 From: Malcolm Parsons Date: Thu, 2 Jul 2015 13:44:47 +0100 Subject: [PATCH 057/226] Get mercurial logs for non-default branches --- codespeed/mercurial.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codespeed/mercurial.py b/codespeed/mercurial.py index 5a670749..d1cf0b49 100644 --- a/codespeed/mercurial.py +++ b/codespeed/mercurial.py @@ -45,8 +45,7 @@ def getlogs(endrev, startrev): updaterepo(endrev.branch.project, update=False) cmd = ["hg", "log", - "-r", "%s:%s" % (endrev.commitid, startrev.commitid), - "-b", "default", + "-r", "%s::%s" % (startrev.commitid, endrev.commitid), "--template", "{rev}:{node|short}\n{node}\n{author|user}\n{author|email}\n{date}\n{desc}\n=newlog=\n"] working_copy = endrev.branch.project.working_copy From 4d6b325a8b5bac870c4f27954a4ea66ee1cce9c7 Mon Sep 17 00:00:00 2001 From: Malcolm Parsons Date: Fri, 16 Oct 2015 16:22:44 +0100 Subject: [PATCH 058/226] Remove duplicate environment column from Result admin --- codespeed/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/admin.py b/codespeed/admin.py index 99148fb6..b74ced44 100644 --- a/codespeed/admin.py +++ b/codespeed/admin.py @@ -56,7 +56,7 @@ class EnvironmentAdmin(admin.ModelAdmin): class ResultAdmin(admin.ModelAdmin): list_display = ('revision', 'benchmark', 'executable', 'environment', - 'value', 'date', 'environment') + 'value', 'date') list_filter = ('environment', 'executable', 'date', 'benchmark') admin.site.register(Result, ResultAdmin) From 3d5ec866fa4c9794e98413e6b47fc426c1171218 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 19:12:37 +0100 Subject: [PATCH 059/226] Update test matrix. Remove Django 1.5, commented out Python 3 entries --- .travis.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9be93cf2..85268825 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,11 @@ language: python python: - "2.6" - "2.7" -# - "3.2" -# - "3.3" -# - "pypy" env: - - DJANGO_VERSION=1.6 - - DJANGO_VERSION=1.5.5 - - DJANGO_VERSION=1.4.9 + - DJANGO_VERSION=1.6.11 + - DJANGO_VERSION=1.4.22 -# command to install dependencies install: - pip install -q Django==$DJANGO_VERSION - python setup.py install From cbaac723b8d9e93b0ccce0173d41865585217a9f Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 19:36:25 +0100 Subject: [PATCH 060/226] Split model and settings tests out to their own modules --- codespeed/tests/test_models.py | 58 ++++ codespeed/tests/test_settings.py | 34 +++ codespeed/tests/test_views.py | 387 +++++++++++++++++++++++++- codespeed/tests/tests.py | 460 ------------------------------- 4 files changed, 474 insertions(+), 465 deletions(-) create mode 100644 codespeed/tests/test_models.py create mode 100644 codespeed/tests/test_settings.py delete mode 100644 codespeed/tests/tests.py diff --git a/codespeed/tests/test_models.py b/codespeed/tests/test_models.py new file mode 100644 index 00000000..74a802c8 --- /dev/null +++ b/codespeed/tests/test_models.py @@ -0,0 +1,58 @@ +import os + +from django.conf import settings +from django.test import TestCase + +from codespeed.models import Project + + +class TestProject(TestCase): + + def setUp(self): + self.github_project = Project( + repo_type='H', repo_path='https://github.com/tobami/codespeed.git') + self.git_project = Project(repo_type='G', + repo_path='/home/foo/codespeed') + + def test_repo_name(self): + """Test that only projects with local repositories have a repo_name attribute + """ + self.assertEqual(self.git_project.repo_name, 'codespeed') + + self.assertRaises(AttributeError, getattr, + self.github_project, 'repo_name') + + def test_working_copy(self): + """Test that only projects with local repositories have a working_copy + attribute + + """ + self.assertEqual(self.git_project.working_copy, + os.path.join(settings.REPOSITORY_BASE_PATH, + self.git_project.repo_name)) + + self.assertRaises( + AttributeError, getattr, self.github_project, 'working_copy') + + def test_github_browsing_url(self): + """If empty, the commit browsing url will be filled in with a default + value when using github repository. + """ + + # It should work with https:// as well as git:// urls + self.github_project.save() + self.assertEquals(self.github_project.commit_browsing_url, + 'https://github.com/tobami/codespeed.git/' + 'commit/{commitid}') + + self.github_project.repo_path = 'git://github.com/tobami/codespeed.git' + self.github_project.save() + self.assertEquals(self.github_project.commit_browsing_url, + 'https://github.com/tobami/codespeed.git/' + 'commit/{commitid}') + + # If filled in, commit browsing url should not change + self.github_project.commit_browsing_url = 'https://example.com/{commitid}' + self.github_project.save() + self.assertEquals(self.github_project.commit_browsing_url, + 'https://example.com/{commitid}') diff --git a/codespeed/tests/test_settings.py b/codespeed/tests/test_settings.py new file mode 100644 index 00000000..f525bd22 --- /dev/null +++ b/codespeed/tests/test_settings.py @@ -0,0 +1,34 @@ +from django.conf import settings +from django.test import TestCase + +from codespeed import settings as default_settings + + +class TestCodespeedSettings(TestCase): + """Test codespeed.settings + """ + + def setUp(self): + self.cs_setting_keys = [key for key in dir(default_settings) if key.isupper()] + + def test_website_name(self): + """See if WEBSITENAME is set + """ + self.assertTrue(default_settings.WEBSITE_NAME) + self.assertEqual(default_settings.WEBSITE_NAME, 'MySpeedSite', + "Change codespeed settings in project.settings") + + def test_keys_in_settings(self): + """Check that all settings attributes from codespeed.settings exist + in django.conf.settings + """ + for k in self.cs_setting_keys: + self.assertTrue(hasattr(settings, k), + "Key {0} is missing in settings.py.".format(k)) + + def test_settings_attributes(self): + """Check if all settings from codespeed.settings equals + django.conf.settings + """ + for k in self.cs_setting_keys: + self.assertEqual(getattr(settings, k), getattr(default_settings, k)) diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py index 9d1113ae..7b0f78a0 100644 --- a/codespeed/tests/test_views.py +++ b/codespeed/tests/test_views.py @@ -1,14 +1,358 @@ +# -*- coding: utf-8 -*- +from datetime import datetime, timedelta +import copy +import json + from django.test import TestCase from django.core.urlresolvers import reverse -from codespeed.models import Environment +from codespeed.models import (Project, Benchmark, Revision, Branch, Executable, + Environment, Result, Report) +from codespeed.views import getbaselineexecutables class TestAddResult(TestCase): def setUp(self): - self.path = reverse('codespeed.views.reports') - self.e = Environment.objects.create(name='Dual Core', cpu='Core 2 Duo 8200') + self.path = reverse('codespeed.views.add_result') + self.e = Environment.objects.create(name='Dual Core', + cpu='Core 2 Duo 8200') + temp = datetime.today() + self.cdate = datetime( + temp.year, temp.month, temp.day, + temp.hour, temp.minute, temp.second) + self.data = { + 'commitid': '23', + 'branch': 'default', + 'project': 'MyProject', + 'executable': 'myexe O3 64bits', + 'benchmark': 'float', + 'environment': 'Dual Core', + 'result_value': 456, + } + + def test_add_correct_result(self): + """Add correct result data""" + response = self.client.post(self.path, self.data) + + # Check that we get a success response + self.assertEquals(response.status_code, 202) + self.assertEquals(response.content, "Result data saved successfully") + + # Check that the data was correctly saved + e = Environment.objects.get(name='Dual Core') + b = Benchmark.objects.get(name='float') + self.assertEquals(b.benchmark_type, "C") + self.assertEquals(b.units, "seconds") + self.assertEquals(b.lessisbetter, True) + p = Project.objects.get(name='MyProject') + branch = Branch.objects.get(name='default', project=p) + r = Revision.objects.get(commitid='23', branch=branch) + i = Executable.objects.get(name='myexe O3 64bits') + res = Result.objects.get( + revision=r, + executable=i, + benchmark=b, + environment=e + ) + self.assertTrue(res.value, 456) + + def test_add_non_default_result(self): + """Add result data with non-mandatory options""" + modified_data = copy.deepcopy(self.data) + revision_date = self.cdate - timedelta(minutes=2) + modified_data['revision_date'] = revision_date + result_date = self.cdate + timedelta(minutes=2) + modified_data['result_date'] = result_date + modified_data['std_dev'] = 1.11111 + modified_data['max'] = 2 + modified_data['min'] = 1.0 + response = self.client.post(self.path, modified_data) + self.assertEquals(response.status_code, 202) + self.assertEquals(response.content, "Result data saved successfully") + e = Environment.objects.get(name='Dual Core') + p = Project.objects.get(name='MyProject') + branch = Branch.objects.get(name='default', project=p) + r = Revision.objects.get(commitid='23', branch=branch) + + # Tweak the resolution down to avoid failing over very slight differences: + self.assertEquals(r.date, revision_date) + + i = Executable.objects.get(name='myexe O3 64bits') + b = Benchmark.objects.get(name='float') + res = Result.objects.get( + revision=r, + executable=i, + benchmark=b, + environment=e + ) + self.assertEquals(res.date, result_date) + self.assertEquals(res.std_dev, 1.11111) + self.assertEquals(res.val_max, 2) + self.assertEquals(res.val_min, 1) + + def test_bad_environment(self): + """Should return 400 when environment does not exist""" + bad_name = '10 Core' + self.data['environment'] = bad_name + response = self.client.post(self.path, self.data) + self.assertEquals(response.status_code, 400) + self.assertEquals(response.content, "Environment " + bad_name + " not found") + self.data['environment'] = 'Dual Core' + + def test_empty_argument(self): + """Should respond 400 when a POST request has an empty argument""" + for key in self.data: + backup = self.data[key] + self.data[key] = "" + response = self.client.post(self.path, self.data) + self.assertEquals(response.status_code, 400) + self.assertEquals( + response.content, 'Value for key "' + key + '" empty in request') + self.data[key] = backup + + def test_missing_argument(self): + """Should respond 400 when a POST request is missing an argument""" + for key in self.data: + backup = self.data[key] + del(self.data[key]) + response = self.client.post(self.path, self.data) + self.assertEquals(response.status_code, 400) + self.assertEquals( + response.content, 'Key "' + key + '" missing from request') + self.data[key] = backup + + def test_report_is_not_created(self): + """Should not create a report when adding a single result""" + self.client.post(self.path, self.data) + number_of_reports = len(Report.objects.all()) + # After adding one result for one revision, there should be no reports + self.assertEquals(number_of_reports, 0) + + def test_report_is_created(self): + """Should create a report when adding a result for two revisions""" + # First result does not create report + self.client.post(self.path, self.data) + + modified_data = copy.deepcopy(self.data) + modified_data['commitid'] = "23233" + # Second result should trigger report creation + self.client.post(self.path, modified_data) + number_of_reports = len(Report.objects.all()) + self.assertEquals(number_of_reports, 1) + + def test_submit_data_with_none_timestamp(self): + """Should add a default revision date when timestamp is None""" + modified_data = copy.deepcopy(self.data) + # The value None will get urlencoded and converted to a "None" string + modified_data['revision_date'] = None + response = self.client.post(self.path, modified_data) + self.assertEquals(response.status_code, 202) + + def test_add_result_with_no_project(self): + """Should add a revision with the project""" + modified_data = copy.deepcopy(self.data) + modified_data['project'] = "My new project" + modified_data['executable'] = "My new executable" + response = self.client.post(self.path, modified_data) + self.assertEquals(response.status_code, 202) + self.assertEquals(response.content, "Result data saved successfully") + + +class TestAddJSONResults(TestCase): + + def setUp(self): + self.path = reverse('codespeed.views.add_json_results') + self.e = Environment(name='bigdog', cpu='Core 2 Duo 8200') + self.e.save() + temp = datetime.today() + self.cdate = datetime( + temp.year, temp.month, temp.day, temp.hour, temp.minute, temp.second) + + self.data = [ + {'commitid': '123', + 'project': 'pypy', + 'branch': 'default', + 'executable': 'pypy-c', + 'benchmark': 'Richards', + 'environment': 'bigdog', + 'result_value': 456}, + {'commitid': '456', + 'project': 'pypy', + 'branch': 'default', + 'executable': 'pypy-c', + 'benchmark': 'Richards', + 'environment': 'bigdog', + 'result_value': 457}, + {'commitid': '456', + 'project': 'pypy', + 'branch': 'default', + 'executable': 'pypy-c', + 'benchmark': 'Richards2', + 'environment': 'bigdog', + 'result_value': 34}, + {'commitid': '789', + 'project': 'pypy', + 'branch': 'default', + 'executable': 'pypy-c', + 'benchmark': 'Richards', + 'environment': 'bigdog', + 'result_value': 458}, + ] + + def test_add_correct_results(self): + """Should add all results when the request data is valid""" + response = self.client.post(self.path, + {'json': json.dumps(self.data)}) + + # Check that we get a success response + self.assertEquals(response.status_code, 202) + self.assertEquals(response.content, + "All result data saved successfully") + + # Check that the data was correctly saved + e = Environment.objects.get(name='bigdog') + b = Benchmark.objects.get(name='Richards') + self.assertEquals(b.benchmark_type, "C") + self.assertEquals(b.units, "seconds") + self.assertEquals(b.lessisbetter, True) + p = Project.objects.get(name='pypy') + branch = Branch.objects.get(name='default', project=p) + r = Revision.objects.get(commitid='123', branch=branch) + i = Executable.objects.get(name='pypy-c') + res = Result.objects.get( + revision=r, + executable=i, + benchmark=b, + environment=e + ) + self.assertTrue(res.value, 456) + resdate = res.date.strftime("%Y%m%dT%H%M%S") + selfdate = self.cdate.strftime("%Y%m%dT%H%M%S") + self.assertTrue(resdate, selfdate) + + r = Revision.objects.get(commitid='456', branch=branch) + res = Result.objects.get( + revision=r, + executable=i, + benchmark=b, + environment=e + ) + self.assertTrue(res.value, 457) + + r = Revision.objects.get(commitid='789', branch=branch) + res = Result.objects.get( + revision=r, + executable=i, + benchmark=b, + environment=e + ) + self.assertTrue(res.value, 458) + + def test_bad_environment(self): + """Add result associated with non-existing environment. + Only change one item in the list. + """ + data = self.data[0] + bad_name = 'bigdog1' + data['environment'] = bad_name + response = self.client.post(self.path, + {'json': json.dumps(self.data)}) + + self.assertEquals(response.status_code, 400) + self.assertEquals(response.content, "Environment " + bad_name + " not found") + data['environment'] = 'bigdog' + + def test_empty_argument(self): + '''Should return 400 when making a request with an empty argument''' + data = self.data[1] + for key in data: + backup = data[key] + data[key] = "" + response = self.client.post(self.path, + {'json': json.dumps(self.data)}) + self.assertEquals(response.status_code, 400) + self.assertEquals(response.content, 'Value for key "' + key + '" empty in request') + data[key] = backup + + def test_missing_argument(self): + '''Should return 400 when making a request with a missing argument''' + data = self.data[2] + for key in data: + backup = data[key] + del(data[key]) + response = self.client.post(self.path, + {'json': json.dumps(self.data)}) + self.assertEquals(response.status_code, 400) + self.assertEquals(response.content, 'Key "' + key + '" missing from request') + data[key] = backup + + def test_report_is_created(self): + '''Should create a report when adding json results for two revisions + plus a third revision with one result less than the last one''' + response = self.client.post(self.path, + {'json': json.dumps(self.data)}) + + # Check that we get a success response + self.assertEquals(response.status_code, 202) + + number_of_reports = len(Report.objects.all()) + # After adding 4 result for 3 revisions, only 2 reports should be created + # The third revision will need an extra result for Richards2 in order + # to trigger report creation + self.assertEquals(number_of_reports, 1) + + +class TestTimeline(TestCase): + fixtures = ["timeline_tests.json"] + + def test_fixture(self): + """Test the loaded fixture data + """ + env = Environment.objects.filter(name="Dual Core") + self.assertEquals(len(env), 1) + benchmarks = Benchmark.objects.filter(name="float") + self.assertEquals(len(benchmarks), 1) + self.assertEquals(benchmarks[0].units, "seconds") + results = benchmarks[0].results.all() + self.assertEquals(len(results), 8) + + def test_gettimelinedata(self): + """Test that gettimelinedata returns correct timeline data + """ + path = reverse('codespeed.views.gettimelinedata') + data = { + "exe": "1,2", + "base": "2+4", + "ben": "float", + "env": "1", + "revs": 2 + } + response = self.client.get(path, data) + self.assertEquals(response.status_code, 200) + responsedata = json.loads(response.content) + self.assertEquals( + responsedata['error'], "None", "there should be no errors") + self.assertEquals( + len(responsedata['timelines']), 1, "there should be 1 benchmark") + self.assertEquals( + len(responsedata['timelines'][0]['branches']['default']), + 2, + "there should be 2 timelines") + self.assertEquals( + len(responsedata['timelines'][0]['branches']['default']['1']), + 2, + "There are 2 datapoints") + self.assertEquals( + responsedata['timelines'][0]['branches']['default']['1'][1], + [u'2011-04-13T17:04:22', 2000.0, 1.11111, u'2', u'default'], + "Wrong data returned: ") + + +class TestReports(TestCase): + + def setUp(self): + Environment.objects.create(name='Dual Core', cpu='Core 2 Duo 8200') self.data = { 'commitid': 'abcd1', 'branch': 'default', @@ -26,7 +370,7 @@ def setUp(self): self.assertEqual(resp.status_code, 202) def test_reports(self): - response = self.client.get(self.path) + response = self.client.get(reverse('codespeed.views.reports')) self.assertEqual(response.status_code, 200) self.assertIn('Latest Results', response.content) @@ -34,6 +378,39 @@ def test_reports(self): self.assertIn(self.data['commitid'], response.content) def test_reports_post_returns_405(self): - response = self.client.post(self.path, {}) + response = self.client.post(reverse('codespeed.views.reports'), {}) self.assertEqual(response.status_code, 405) + + +class TestViewHelpers(TestCase): + """Test helper functions in codespeed.views""" + + def setUp(self): + self.project = Project.objects.create(name='Test') + self.executable = Executable.objects.create( + name='TestExecutable', project=self.project) + self.branch = Branch.objects.create(name='master', project=self.project) + + def test_get_baseline_executables(self): + # No revisions, no baseline + result = getbaselineexecutables() + self.assertEqual(len(result), 1) + self.assertEqual(result[0]['executable'], 'none') + + # Check that a tagged revision will be included as baseline + revision1 = Revision.objects.create(commitid='1', tag='0.1', branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 2) + self.assertEqual(result[0]['executable'], 'none') + self.assertEqual(result[1]['executable'], self.executable) + self.assertEqual(result[1]['revision'], revision1) + + revision2 = Revision.objects.create(commitid='2', tag='0.2', branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 3) + + # An untagged revision will not be available as baseline + Revision.objects.create(commitid='3', branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 3) diff --git a/codespeed/tests/tests.py b/codespeed/tests/tests.py deleted file mode 100644 index be411cfb..00000000 --- a/codespeed/tests/tests.py +++ /dev/null @@ -1,460 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import datetime, timedelta -import copy -import json -import os - -from django.test import TestCase -from django.core.urlresolvers import reverse -from django.conf import settings - -from codespeed.models import (Project, Benchmark, Revision, Branch, Executable, - Environment, Result, Report) -from codespeed.views import getbaselineexecutables -from codespeed import settings as default_settings - - -class TestAddResult(TestCase): - - def setUp(self): - self.path = reverse('codespeed.views.add_result') - self.e = Environment.objects.create(name='Dual Core', cpu='Core 2 Duo 8200') - temp = datetime.today() - self.cdate = datetime( - temp.year, temp.month, temp.day, temp.hour, temp.minute, temp.second) - self.data = { - 'commitid': '23', - 'branch': 'default', - 'project': 'MyProject', - 'executable': 'myexe O3 64bits', - 'benchmark': 'float', - 'environment': 'Dual Core', - 'result_value': 456, - } - - def test_add_correct_result(self): - """Add correct result data""" - response = self.client.post(self.path, self.data) - - # Check that we get a success response - self.assertEquals(response.status_code, 202) - self.assertEquals(response.content, "Result data saved successfully") - - # Check that the data was correctly saved - e = Environment.objects.get(name='Dual Core') - b = Benchmark.objects.get(name='float') - self.assertEquals(b.benchmark_type, "C") - self.assertEquals(b.units, "seconds") - self.assertEquals(b.lessisbetter, True) - p = Project.objects.get(name='MyProject') - branch = Branch.objects.get(name='default', project=p) - r = Revision.objects.get(commitid='23', branch=branch) - i = Executable.objects.get(name='myexe O3 64bits') - res = Result.objects.get( - revision=r, - executable=i, - benchmark=b, - environment=e - ) - self.assertTrue(res.value, 456) - - def test_add_non_default_result(self): - """Add result data with non-mandatory options""" - modified_data = copy.deepcopy(self.data) - revision_date = self.cdate - timedelta(minutes=2) - modified_data['revision_date'] = revision_date - result_date = self.cdate + timedelta(minutes=2) - modified_data['result_date'] = result_date - modified_data['std_dev'] = 1.11111 - modified_data['max'] = 2 - modified_data['min'] = 1.0 - response = self.client.post(self.path, modified_data) - self.assertEquals(response.status_code, 202) - self.assertEquals(response.content, "Result data saved successfully") - e = Environment.objects.get(name='Dual Core') - p = Project.objects.get(name='MyProject') - branch = Branch.objects.get(name='default', project=p) - r = Revision.objects.get(commitid='23', branch=branch) - - # Tweak the resolution down to avoid failing over very slight differences: - self.assertEquals(r.date, revision_date) - - i = Executable.objects.get(name='myexe O3 64bits') - b = Benchmark.objects.get(name='float') - res = Result.objects.get( - revision=r, - executable=i, - benchmark=b, - environment=e - ) - self.assertEquals(res.date, result_date) - self.assertEquals(res.std_dev, 1.11111) - self.assertEquals(res.val_max, 2) - self.assertEquals(res.val_min, 1) - - def test_bad_environment(self): - """Should return 400 when environment does not exist""" - bad_name = '10 Core' - self.data['environment'] = bad_name - response = self.client.post(self.path, self.data) - self.assertEquals(response.status_code, 400) - self.assertEquals(response.content, "Environment " + bad_name + " not found") - self.data['environment'] = 'Dual Core' - - def test_empty_argument(self): - """Should respond 400 when a POST request has an empty argument""" - for key in self.data: - backup = self.data[key] - self.data[key] = "" - response = self.client.post(self.path, self.data) - self.assertEquals(response.status_code, 400) - self.assertEquals( - response.content, 'Value for key "' + key + '" empty in request') - self.data[key] = backup - - def test_missing_argument(self): - """Should respond 400 when a POST request is missing an argument""" - for key in self.data: - backup = self.data[key] - del(self.data[key]) - response = self.client.post(self.path, self.data) - self.assertEquals(response.status_code, 400) - self.assertEquals( - response.content, 'Key "' + key + '" missing from request') - self.data[key] = backup - - def test_report_is_not_created(self): - '''Should not create a report when adding a single result''' - response = self.client.post(self.path, self.data) - number_of_reports = len(Report.objects.all()) - # After adding one result for one revision, there should be no reports - self.assertEquals(number_of_reports, 0) - - def test_report_is_created(self): - """Should create a report when adding a result for two revisions""" - response = self.client.post(self.path, self.data) - - modified_data = copy.deepcopy(self.data) - modified_data['commitid'] = "23233" - response = self.client.post(self.path, modified_data) - number_of_reports = len(Report.objects.all()) - # After adding a result for a second revision, a report should be created - self.assertEquals(number_of_reports, 1) - - def test_submit_data_with_none_timestamp(self): - """Should add a default revision date when timestamp is None""" - modified_data = copy.deepcopy(self.data) - # The value None will get urlencoded and converted to a "None" string - modified_data['revision_date'] = None - response = self.client.post(self.path, modified_data) - self.assertEquals(response.status_code, 202) - - def test_add_result_with_no_project(self): - """Should add a revision with the project""" - modified_data = copy.deepcopy(self.data) - modified_data['project'] = "My new project" - modified_data['executable'] = "My new executable" - response = self.client.post(self.path, modified_data) - self.assertEquals(response.status_code, 202) - self.assertEquals(response.content, "Result data saved successfully") - - -class TestAddJSONResults(TestCase): - - def setUp(self): - self.path = reverse('codespeed.views.add_json_results') - self.e = Environment(name='bigdog', cpu='Core 2 Duo 8200') - self.e.save() - temp = datetime.today() - self.cdate = datetime( - temp.year, temp.month, temp.day, temp.hour, temp.minute, temp.second) - - self.data = [ - {'commitid': '123', - 'project': 'pypy', - 'branch': 'default', - 'executable': 'pypy-c', - 'benchmark': 'Richards', - 'environment': 'bigdog', - 'result_value': 456}, - {'commitid': '456', - 'project': 'pypy', - 'branch': 'default', - 'executable': 'pypy-c', - 'benchmark': 'Richards', - 'environment': 'bigdog', - 'result_value': 457}, - {'commitid': '456', - 'project': 'pypy', - 'branch': 'default', - 'executable': 'pypy-c', - 'benchmark': 'Richards2', - 'environment': 'bigdog', - 'result_value': 34}, - {'commitid': '789', - 'project': 'pypy', - 'branch': 'default', - 'executable': 'pypy-c', - 'benchmark': 'Richards', - 'environment': 'bigdog', - 'result_value': 458}, - ] - - def test_add_correct_results(self): - """Should add all results when the request data is valid""" - response = self.client.post(self.path, - {'json': json.dumps(self.data)}) - - # Check that we get a success response - self.assertEquals(response.status_code, 202) - self.assertEquals(response.content, - "All result data saved successfully") - - # Check that the data was correctly saved - e = Environment.objects.get(name='bigdog') - b = Benchmark.objects.get(name='Richards') - self.assertEquals(b.benchmark_type, "C") - self.assertEquals(b.units, "seconds") - self.assertEquals(b.lessisbetter, True) - p = Project.objects.get(name='pypy') - branch = Branch.objects.get(name='default', project=p) - r = Revision.objects.get(commitid='123', branch=branch) - i = Executable.objects.get(name='pypy-c') - res = Result.objects.get( - revision=r, - executable=i, - benchmark=b, - environment=e - ) - self.assertTrue(res.value, 456) - resdate = res.date.strftime("%Y%m%dT%H%M%S") - selfdate = self.cdate.strftime("%Y%m%dT%H%M%S") - self.assertTrue(resdate, selfdate) - - r = Revision.objects.get(commitid='456', branch=branch) - res = Result.objects.get( - revision=r, - executable=i, - benchmark=b, - environment=e - ) - self.assertTrue(res.value, 457) - - r = Revision.objects.get(commitid='789', branch=branch) - res = Result.objects.get( - revision=r, - executable=i, - benchmark=b, - environment=e - ) - self.assertTrue(res.value, 458) - - def test_bad_environment(self): - """Add result associated with non-existing environment. - Only change one item in the list. - """ - data = self.data[0] - bad_name = 'bigdog1' - data['environment'] = bad_name - response = self.client.post(self.path, - {'json': json.dumps(self.data)}) - - self.assertEquals(response.status_code, 400) - self.assertEquals(response.content, "Environment " + bad_name + " not found") - data['environment'] = 'bigdog' - - def test_empty_argument(self): - '''Should return 400 when making a request with an empty argument''' - data = self.data[1] - for key in data: - backup = data[key] - data[key] = "" - response = self.client.post(self.path, - {'json': json.dumps(self.data)}) - self.assertEquals(response.status_code, 400) - self.assertEquals(response.content, 'Value for key "' + key + '" empty in request') - data[key] = backup - - def test_missing_argument(self): - '''Should return 400 when making a request with a missing argument''' - data = self.data[2] - for key in data: - backup = data[key] - del(data[key]) - response = self.client.post(self.path, - {'json': json.dumps(self.data)}) - self.assertEquals(response.status_code, 400) - self.assertEquals(response.content, 'Key "' + key + '" missing from request') - data[key] = backup - - def test_report_is_created(self): - '''Should create a report when adding json results for two revisions - plus a third revision with one result less than the last one''' - response = self.client.post(self.path, - {'json': json.dumps(self.data)}) - - # Check that we get a success response - self.assertEquals(response.status_code, 202) - - number_of_reports = len(Report.objects.all()) - # After adding 4 result for 3 revisions, only 2 reports should be created - # The third revision will need an extra result for Richards2 in order - # to trigger report creation - self.assertEquals(number_of_reports, 1) - - -class TestTimeline(TestCase): - fixtures = ["timeline_tests.json"] - - def test_fixture(self): - """Test the loaded fixture data - """ - env = Environment.objects.filter(name="Dual Core") - self.assertEquals(len(env), 1) - benchmarks = Benchmark.objects.filter(name="float") - self.assertEquals(len(benchmarks), 1) - self.assertEquals(benchmarks[0].units, "seconds") - results = benchmarks[0].results.all() - self.assertEquals(len(results), 8) - - def test_gettimelinedata(self): - """Test that gettimelinedata returns correct timeline data - """ - path = reverse('codespeed.views.gettimelinedata') - data = { - "exe": "1,2", - "base": "2+4", - "ben": "float", - "env": "1", - "revs": 2 - } - response = self.client.get(path, data) - self.assertEquals(response.status_code, 200) - responsedata = json.loads(response.content) - self.assertEquals( - responsedata['error'], "None", "there should be no errors") - self.assertEquals( - len(responsedata['timelines']), 1, "there should be 1 benchmark") - self.assertEquals( - len(responsedata['timelines'][0]['branches']['default']), - 2, - "there should be 2 timelines") - self.assertEquals( - len(responsedata['timelines'][0]['branches']['default']['1']), - 2, - "There are 2 datapoints") - self.assertEquals( - responsedata['timelines'][0]['branches']['default']['1'][1], - [u'2011-04-13T17:04:22', 2000.0, 1.11111, u'2', u'default'], - "Wrong data returned: ") - - -class TestCodespeedSettings(TestCase): - """Test codespeed.settings - """ - - def setUp(self): - self.cs_setting_keys = [key for key in dir(default_settings) if key.isupper()] - - def test_website_name(self): - """See if WEBSITENAME is set - """ - self.assertTrue(default_settings.WEBSITE_NAME) - self.assertEqual(default_settings.WEBSITE_NAME, 'MySpeedSite', - "Change codespeed settings in project.settings") - - def test_keys_in_settings(self): - """Check that all settings attributes from codespeed.settings exist - in django.conf.settings - """ - for k in self.cs_setting_keys: - self.assertTrue(hasattr(settings, k), - "Key {0} is missing in settings.py.".format(k)) - - def test_settings_attributes(self): - """Check if all settings from codespeed.settings equals - django.conf.settings - """ - for k in self.cs_setting_keys: - self.assertEqual(getattr(settings, k), getattr(default_settings, k)) - - -class TestViewHelpers(TestCase): - """Test helper functions in codespeed.views""" - - def setUp(self): - self.project = Project.objects.create(name='Test') - self.executable = Executable.objects.create( - name='TestExecutable', project=self.project) - self.branch = Branch.objects.create(name='master', project=self.project) - - def test_get_baseline_executables(self): - # No revisions, no baseline - result = getbaselineexecutables() - self.assertEqual(len(result), 1) - self.assertEqual(result[0]['executable'], 'none') - - # Check that a tagged revision will be included as baseline - revision1 = Revision.objects.create(commitid='1', tag='0.1', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 2) - self.assertEqual(result[0]['executable'], 'none') - self.assertEqual(result[1]['executable'], self.executable) - self.assertEqual(result[1]['revision'], revision1) - - revision2 = Revision.objects.create(commitid='2', tag='0.2', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 3) - - # An untagged revision will not be available as baseline - Revision.objects.create(commitid='3', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 3) - - -class TestProject(TestCase): - - def setUp(self): - self.github_project = Project(repo_type='H', - repo_path='https://github.com/tobami/codespeed.git') - self.git_project = Project(repo_type='G', repo_path='/home/foo/codespeed') - - def test_repo_name(self): - """Test that only projects with local repositories have a repo_name attribute - """ - self.assertEqual(self.git_project.repo_name, 'codespeed') - - self.assertRaises(AttributeError, getattr, self.github_project, 'repo_name') - - def test_working_copy(self): - """Test that only projects with local repositories have a working_copy attribute - """ - self.assertEqual(self.git_project.working_copy, - os.path.join(settings.REPOSITORY_BASE_PATH, - self.git_project.repo_name)) - - self.assertRaises( - AttributeError, getattr, self.github_project, 'working_copy') - - def test_github_browsing_url(self): - """If empty, the commit browsing url will be filled in with a default - value when using github repository. - """ - - # It should work with https:// as well as git:// urls - self.github_project.save() - self.assertEquals(self.github_project.commit_browsing_url, - 'https://github.com/tobami/codespeed.git/' - 'commit/{commitid}') - - self.github_project.repo_path = 'git://github.com/tobami/codespeed.git' - self.github_project.save() - self.assertEquals(self.github_project.commit_browsing_url, - 'https://github.com/tobami/codespeed.git/' - 'commit/{commitid}') - - # If filled in, commit browsing url should not change - self.github_project.commit_browsing_url = 'https://example.com/{commitid}' - self.github_project.save() - self.assertEquals(self.github_project.commit_browsing_url, - 'https://example.com/{commitid}') From a96c5f80c9f4a7c546615635b075f19b4a569e61 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 19:48:35 +0100 Subject: [PATCH 061/226] Move view helper functions to domain module --- codespeed/domain.py | 106 ++++++++++++++++++++++++++++++ codespeed/tests/test_domain.py | 40 ++++++++++++ codespeed/tests/test_models.py | 1 + codespeed/tests/test_views.py | 37 +---------- codespeed/views.py | 115 +++------------------------------ 5 files changed, 158 insertions(+), 141 deletions(-) create mode 100644 codespeed/domain.py create mode 100644 codespeed/tests/test_domain.py diff --git a/codespeed/domain.py b/codespeed/domain.py new file mode 100644 index 00000000..7677b022 --- /dev/null +++ b/codespeed/domain.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +import logging + +from django.conf import settings +from codespeed.models import Executable, Revision + +logger = logging.getLogger(__name__) + + +def get_default_environment(enviros, data, multi=False): + """Returns the default environment. Preference level is: + * Present in URL parameters (permalinks) + * Value in settings.py + * First Environment ID + + """ + defaultenviros = [] + # Use permalink values + if 'env' in data: + for env_value in data['env'].split(","): + for env in enviros: + try: + env_id = int(env_value) + except ValueError: + # Not an int + continue + for env in enviros: + if env_id == env.id: + defaultenviros.append(env) + if not multi: + break + # Use settings.py value + if not defaultenviros and not multi: + if (hasattr(settings, 'DEF_ENVIRONMENT') and + settings.DEF_ENVIRONMENT is not None): + for env in enviros: + if settings.DEF_ENVIRONMENT == env.name: + defaultenviros.append(env) + break + # Last fallback + if not defaultenviros: + defaultenviros = enviros + if multi: + return defaultenviros + else: + return defaultenviros[0] + + +def getbaselineexecutables(): + baseline = [{ + 'key': "none", + 'name': "None", + 'executable': "none", + 'revision': "none", + }] + executables = Executable.objects.select_related('project') + revs = Revision.objects.exclude(tag="").select_related('branch__project') + maxlen = 22 + for rev in revs: + # Add executables that correspond to each tagged revision. + for exe in [e for e in executables if e.project == rev.branch.project]: + exestring = str(exe) + if len(exestring) > maxlen: + exestring = str(exe)[0:maxlen] + "..." + name = exestring + " " + rev.tag + key = str(exe.id) + "+" + str(rev.id) + baseline.append({ + 'key': key, + 'executable': exe, + 'revision': rev, + 'name': name, + }) + # move default to first place + if hasattr(settings, 'DEF_BASELINE') and settings.DEF_BASELINE is not None: + try: + exename = settings.DEF_BASELINE['executable'] + commitid = settings.DEF_BASELINE['revision'] + for base in baseline: + if base['key'] == "none": + continue + if (base['executable'].name == exename and + base['revision'].commitid == commitid): + baseline.remove(base) + baseline.insert(1, base) + break + except KeyError: + # TODO: write to server logs + # error in settings.DEF_BASELINE + pass + return baseline + + +def getdefaultexecutable(): + default = None + if (hasattr(settings, 'DEF_EXECUTABLE') and + settings.DEF_EXECUTABLE is not None): + try: + default = Executable.objects.get(name=settings.DEF_EXECUTABLE) + except Executable.DoesNotExist: + pass + if default is None: + execquery = Executable.objects.filter(project__track=True) + if len(execquery): + default = execquery[0] + + return default diff --git a/codespeed/tests/test_domain.py b/codespeed/tests/test_domain.py new file mode 100644 index 00000000..44eb1fb4 --- /dev/null +++ b/codespeed/tests/test_domain.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from django.test import TestCase + +from codespeed.models import Project, Executable, Branch, Revision +from codespeed.views import getbaselineexecutables + + +class TestGetBaselineExecutables(TestCase): + """Test helper functions in codespeed.views""" + + def setUp(self): + self.project = Project.objects.create(name='Test') + self.executable = Executable.objects.create( + name='TestExecutable', project=self.project) + self.branch = Branch.objects.create(name='master', + project=self.project) + + def test_get_baseline_executables(self): + # No revisions, no baseline + result = getbaselineexecutables() + self.assertEqual(len(result), 1) + self.assertEqual(result[0]['executable'], 'none') + + # Check that a tagged revision will be included as baseline + revision1 = Revision.objects.create(commitid='1', tag='0.1', + branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 2) + self.assertEqual(result[0]['executable'], 'none') + self.assertEqual(result[1]['executable'], self.executable) + self.assertEqual(result[1]['revision'], revision1) + + Revision.objects.create(commitid='2', tag='0.2', branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 3) + + # An untagged revision will not be available as baseline + Revision.objects.create(commitid='3', branch=self.branch) + result = getbaselineexecutables() + self.assertEqual(len(result), 3) diff --git a/codespeed/tests/test_models.py b/codespeed/tests/test_models.py index 74a802c8..eb4d04ab 100644 --- a/codespeed/tests/test_models.py +++ b/codespeed/tests/test_models.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os from django.conf import settings diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py index 7b0f78a0..e65a9ffb 100644 --- a/codespeed/tests/test_views.py +++ b/codespeed/tests/test_views.py @@ -8,7 +8,6 @@ from codespeed.models import (Project, Benchmark, Revision, Branch, Executable, Environment, Result, Report) -from codespeed.views import getbaselineexecutables class TestAddResult(TestCase): @@ -362,7 +361,8 @@ def setUp(self): 'environment': 'Dual Core', 'result_value': 200, } - resp = self.client.post(reverse('codespeed.views.add_result'), self.data) + resp = self.client.post(reverse('codespeed.views.add_result'), + self.data) self.assertEqual(resp.status_code, 202) self.data['commitid'] = "abcd2" self.data['result_value'] = 150 @@ -381,36 +381,3 @@ def test_reports_post_returns_405(self): response = self.client.post(reverse('codespeed.views.reports'), {}) self.assertEqual(response.status_code, 405) - - -class TestViewHelpers(TestCase): - """Test helper functions in codespeed.views""" - - def setUp(self): - self.project = Project.objects.create(name='Test') - self.executable = Executable.objects.create( - name='TestExecutable', project=self.project) - self.branch = Branch.objects.create(name='master', project=self.project) - - def test_get_baseline_executables(self): - # No revisions, no baseline - result = getbaselineexecutables() - self.assertEqual(len(result), 1) - self.assertEqual(result[0]['executable'], 'none') - - # Check that a tagged revision will be included as baseline - revision1 = Revision.objects.create(commitid='1', tag='0.1', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 2) - self.assertEqual(result[0]['executable'], 'none') - self.assertEqual(result[1]['executable'], self.executable) - self.assertEqual(result[1]['revision'], revision1) - - revision2 = Revision.objects.create(commitid='2', tag='0.2', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 3) - - # An untagged revision will not be available as baseline - Revision.objects.create(commitid='3', branch=self.branch) - result = getbaselineexecutables() - self.assertEqual(len(result), 3) diff --git a/codespeed/views.py b/codespeed/views.py index 157bf87a..691508c0 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -15,7 +15,8 @@ from codespeed.models import (Environment, Report, Project, Revision, Result, Executable, Benchmark, Branch) - +from codespeed.domain import (get_default_environment, getbaselineexecutables, + getdefaultexecutable) logger = logging.getLogger(__name__) @@ -51,104 +52,6 @@ def no_data_found(request): }, context_instance=RequestContext(request)) -def getbaselineexecutables(): - baseline = [{ - 'key': "none", - 'name': "None", - 'executable': "none", - 'revision': "none", - }] - executables = Executable.objects.select_related('project') - revs = Revision.objects.exclude(tag="").select_related('branch__project') - maxlen = 22 - for rev in revs: - # Add executables that correspond to each tagged revision. - for exe in [e for e in executables if e.project == rev.branch.project]: - exestring = str(exe) - if len(exestring) > maxlen: - exestring = str(exe)[0:maxlen] + "..." - name = exestring + " " + rev.tag - key = str(exe.id) + "+" + str(rev.id) - baseline.append({ - 'key': key, - 'executable': exe, - 'revision': rev, - 'name': name, - }) - # move default to first place - if hasattr(settings, 'DEF_BASELINE') and settings.DEF_BASELINE is not None: - try: - exename = settings.DEF_BASELINE['executable'] - commitid = settings.DEF_BASELINE['revision'] - for base in baseline: - if base['key'] == "none": - continue - if (base['executable'].name == exename and - base['revision'].commitid == commitid): - baseline.remove(base) - baseline.insert(1, base) - break - except KeyError: - # TODO: write to server logs - #error in settings.DEF_BASELINE - pass - return baseline - - -def get_default_environment(enviros, data, multi=False): - """Returns the default environment. Preference level is: - * Present in URL parameters (permalinks) - * Value in settings.py - * First Environment ID - - """ - defaultenviros = [] - # Use permalink values - if 'env' in data: - for env_value in data['env'].split(","): - for env in enviros: - try: - env_id = int(env_value) - except ValueError: - # Not an int - continue - for env in enviros: - if env_id == env.id: - defaultenviros.append(env) - if not multi: - break - # Use settings.py value - if not defaultenviros and not multi: - if (hasattr(settings, 'DEF_ENVIRONMENT') and - settings.DEF_ENVIRONMENT is not None): - for env in enviros: - if settings.DEF_ENVIRONMENT == env.name: - defaultenviros.append(env) - break - # Last fallback - if not defaultenviros: - defaultenviros = enviros - if multi: - return defaultenviros - else: - return defaultenviros[0] - - -def getdefaultexecutable(): - default = None - if hasattr(settings, 'DEF_EXECUTABLE') and settings.DEF_EXECUTABLE is not None: - try: - default = Executable.objects.get(name=settings.DEF_EXECUTABLE) - except Executable.DoesNotExist: - pass - if default is None: - execquery = Executable.objects.filter(project__track=True) - if len(execquery): - default = execquery[0] - - return default - - def getcomparisonexes(): all_executables = {} exekeys = [] @@ -689,7 +592,7 @@ def changes(request): selectedrevision = Revision.objects.get( commitid__startswith=commitid, branch=branch ) - if not selectedrevision in revisionlists[selectedrevision.project.name]: + if selectedrevision not in revisionlists[selectedrevision.project.name]: revisionlists[selectedrevision.project.name].append(selectedrevision) except Revision.DoesNotExist: selectedrevision = lastrevisions[0] @@ -735,11 +638,11 @@ def reports(request): context['significant_reports'] = \ Report.objects.filter( revision__branch__name=settings.DEF_BRANCH, - colorcode__in = ('red','green') + colorcode__in=('red', 'green') ).order_by('-revision__date')[:10] - return render_to_response('codespeed/reports.html', - context, context_instance=RequestContext(request)) + return render_to_response('codespeed/reports.html', context, + context_instance=RequestContext(request)) @require_GET @@ -780,7 +683,7 @@ def displaylogs(request): rev, e, exc_info=True) error = repr(e) - # add commit browsing url to logs + # Add commit browsing url to logs project = rev.branch.project for log in logs: log['commit_browse_url'] = project.commit_browsing_url.format(**log) @@ -851,7 +754,7 @@ def validate_result(item): error = True for key in mandatory_data: - if not key in item: + if key not in item: return 'Key "' + key + '" missing from request', error elif key in item and item[key] == "": return 'Value for key "' + key + '" empty in request', error @@ -995,7 +898,7 @@ def add_json_results(request): if error: logger.debug( "add_json_results: could not save item %d because %s" % ( - i, response)) + i, response)) return HttpResponseBadRequest(response) else: unique_reports.add(response) From 0d130e9c69ce0c6e148f734b10965d25fa812988 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 20:02:36 +0100 Subject: [PATCH 062/226] Move remaining non-view functions to domain module --- codespeed/domain.py | 219 +++++++++++++++++++++++++++++++++++++++++++- codespeed/views.py | 219 +------------------------------------------- 2 files changed, 221 insertions(+), 217 deletions(-) diff --git a/codespeed/domain.py b/codespeed/domain.py index 7677b022..d30d7d29 100644 --- a/codespeed/domain.py +++ b/codespeed/domain.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- import logging +from datetime import datetime +from django.core.exceptions import ValidationError from django.conf import settings -from codespeed.models import Executable, Revision +from codespeed.models import (Environment, Executable, Revision, + Project, Branch, Benchmark, + Result, Report) logger = logging.getLogger(__name__) @@ -104,3 +108,216 @@ def getdefaultexecutable(): default = execquery[0] return default + + +def getcomparisonexes(): + all_executables = {} + exekeys = [] + baselines = getbaselineexecutables() + for proj in Project.objects.all(): + executables = [] + executablekeys = [] + maxlen = 20 + # add all tagged revs for any project + for exe in baselines: + if exe['key'] is not "none" and exe['executable'].project == proj: + executablekeys.append(exe['key']) + executables.append(exe) + + # add latest revs of the project + branches = Branch.objects.filter(project=proj) + for branch in branches: + try: + rev = Revision.objects.filter(branch=branch).latest('date') + except Revision.DoesNotExist: + continue + # Now only append when tag == "", + # because we already added tagged revisions + if rev.tag == "": + for exe in Executable.objects.filter(project=proj): + exestring = str(exe) + if len(exestring) > maxlen: + exestring = str(exe)[0:maxlen] + "..." + name = exestring + " latest" + if branch.name != 'default': + name += " in branch '" + branch.name + "'" + key = str(exe.id) + "+L+" + branch.name + executablekeys.append(key) + executables.append({ + 'key': key, + 'executable': exe, + 'revision': rev, + 'name': name, + }) + all_executables[proj] = executables + exekeys += executablekeys + return all_executables, exekeys + + +def getcommitlogs(rev, startrev, update=False): + logs = [] + + if rev.branch.project.repo_type == 'S': + from subversion import getlogs, updaterepo + elif rev.branch.project.repo_type == 'M': + from mercurial import getlogs, updaterepo + elif rev.branch.project.repo_type == 'G': + from git import getlogs, updaterepo + elif rev.branch.project.repo_type == 'H': + from github import getlogs, updaterepo + else: + if rev.branch.project.repo_type not in ("N", ""): + logger.warning("Don't know how to retrieve logs from %s project", + rev.branch.project.get_repo_type_display()) + return logs + + if update: + updaterepo(rev.branch.project) + + logs = getlogs(rev, startrev) + + # Remove last log because the startrev log shouldn't be shown + if len(logs) > 1 and logs[-1].get('commitid') == startrev.commitid: + logs.pop() + + return logs + + +def saverevisioninfo(rev): + log = getcommitlogs(rev, rev, update=True) + + if log: + log = log[0] + rev.author = log['author'] + rev.date = log['date'] + rev.message = log['message'] + + +def validate_result(item): + """ + Validates that a result dictionary has all needed parameters + + It returns a tuple + Environment, False when no errors where found + Errormessage, True when there is an error + """ + mandatory_data = [ + 'commitid', + 'branch', + 'project', + 'executable', + 'benchmark', + 'environment', + 'result_value', + ] + + error = True + for key in mandatory_data: + if key not in item: + return 'Key "' + key + '" missing from request', error + elif key in item and item[key] == "": + return 'Value for key "' + key + '" empty in request', error + + # Check that the Environment exists + try: + e = Environment.objects.get(name=item['environment']) + error = False + return e, error + except Environment.DoesNotExist: + return "Environment %(environment)s not found" % item, error + + +def create_report_if_enough_data(rev, exe, e): + """Triggers Report creation when there are enough results""" + last_revs = Revision.objects.filter( + branch=rev.branch + ).order_by('-date')[:2] + if len(last_revs) > 1: + current_results = rev.results.filter(executable=exe, environment=e) + last_results = last_revs[1].results.filter( + executable=exe, environment=e) + # If there is are at least as many results as in the last revision, + # create new report + if len(current_results) >= len(last_results): + logger.debug("create_report_if_enough_data: About to create new report") + report, created = Report.objects.get_or_create( + executable=exe, environment=e, revision=rev + ) + report.full_clean() + report.save() + logger.debug("create_report_if_enough_data: Created new report.") + + +def save_result(data): + res, error = validate_result(data) + if error: + return res, True + else: + assert(isinstance(res, Environment)) + env = res + + p, created = Project.objects.get_or_create(name=data["project"]) + branch, created = Branch.objects.get_or_create(name=data["branch"], + project=p) + b, created = Benchmark.objects.get_or_create(name=data["benchmark"]) + + if created: + if "description" in data: + b.description = data["description"] + if "units" in data: + b.units = data["units"] + if "units_title" in data: + b.units_title = data["units_title"] + if "lessisbetter" in data: + b.lessisbetter = data["lessisbetter"] + b.full_clean() + b.save() + + try: + rev = branch.revisions.get(commitid=data['commitid']) + except Revision.DoesNotExist: + rev_date = data.get("revision_date") + # "None" (as string) can happen when we urlencode the POST data + if not rev_date or rev_date in ["", "None"]: + rev_date = datetime.today() + rev = Revision(branch=branch, project=p, commitid=data['commitid'], + date=rev_date) + try: + rev.full_clean() + except ValidationError as e: + return str(e), True + if p.repo_type not in ("N", ""): + try: + saverevisioninfo(rev) + except RuntimeError as e: + logger.warning("unable to save revision %s info: %s", rev, e, + exc_info=True) + rev.save() + + exe, created = Executable.objects.get_or_create( + name=data['executable'], + project=p + ) + + try: + r = Result.objects.get( + revision=rev, executable=exe, benchmark=b, environment=env) + except Result.DoesNotExist: + r = Result(revision=rev, executable=exe, benchmark=b, environment=env) + + r.value = data["result_value"] + if 'result_date' in data: + r.date = data["result_date"] + elif rev.date: + r.date = rev.date + else: + r.date = datetime.now() + + r.std_dev = data.get('std_dev') + r.val_min = data.get('min') + r.val_max = data.get('max') + + r.full_clean() + r.save() + + return (rev, exe, env), False diff --git a/codespeed/views.py b/codespeed/views.py index 691508c0..24189416 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- -from datetime import datetime import json import logging -from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.http import (HttpResponse, Http404, HttpResponseNotAllowed, HttpResponseBadRequest) @@ -16,7 +14,9 @@ from codespeed.models import (Environment, Report, Project, Revision, Result, Executable, Benchmark, Branch) from codespeed.domain import (get_default_environment, getbaselineexecutables, - getdefaultexecutable) + getdefaultexecutable, getcomparisonexes, + getcommitlogs, save_result, + create_report_if_enough_data) logger = logging.getLogger(__name__) @@ -52,50 +52,6 @@ def no_data_found(request): }, context_instance=RequestContext(request)) -def getcomparisonexes(): - all_executables = {} - exekeys = [] - baselines = getbaselineexecutables() - for proj in Project.objects.all(): - executables = [] - executablekeys = [] - maxlen = 20 - # add all tagged revs for any project - for exe in baselines: - if exe['key'] is not "none" and exe['executable'].project == proj: - executablekeys.append(exe['key']) - executables.append(exe) - - # add latest revs of the project - branches = Branch.objects.filter(project=proj) - for branch in branches: - try: - rev = Revision.objects.filter(branch=branch).latest('date') - except Revision.DoesNotExist: - continue - # Now only append when tag == "", - # because we already added tagged revisions - if rev.tag == "": - for exe in Executable.objects.filter(project=proj): - exestring = str(exe) - if len(exestring) > maxlen: - exestring = str(exe)[0:maxlen] + "..." - name = exestring + " latest" - if branch.name != 'default': - name += " in branch '" + branch.name + "'" - key = str(exe.id) + "+L+" + branch.name - executablekeys.append(key) - executables.append({ - 'key': key, - 'executable': exe, - 'revision': rev, - 'name': name, - }) - all_executables[proj] = executables - exekeys += executablekeys - return all_executables, exekeys - - @require_GET def getcomparisondata(request): executables, exekeys = getcomparisonexes() @@ -695,175 +651,6 @@ def displaylogs(request): context_instance=RequestContext(request)) -def getcommitlogs(rev, startrev, update=False): - logs = [] - - if rev.branch.project.repo_type == 'S': - from subversion import getlogs, updaterepo - elif rev.branch.project.repo_type == 'M': - from mercurial import getlogs, updaterepo - elif rev.branch.project.repo_type == 'G': - from git import getlogs, updaterepo - elif rev.branch.project.repo_type == 'H': - from github import getlogs, updaterepo - else: - if rev.branch.project.repo_type not in ("N", ""): - logger.warning("Don't know how to retrieve logs from %s project", - rev.branch.project.get_repo_type_display()) - return logs - - if update: - updaterepo(rev.branch.project) - - logs = getlogs(rev, startrev) - - # Remove last log because the startrev log shouldn't be shown - if len(logs) > 1 and logs[-1].get('commitid') == startrev.commitid: - logs.pop() - - return logs - - -def saverevisioninfo(rev): - log = getcommitlogs(rev, rev, update=True) - - if log: - log = log[0] - rev.author = log['author'] - rev.date = log['date'] - rev.message = log['message'] - - -def validate_result(item): - """ - Validates that a result dictionary has all needed parameters - - It returns a tuple - Environment, False when no errors where found - Errormessage, True when there is an error - """ - mandatory_data = [ - 'commitid', - 'branch', - 'project', - 'executable', - 'benchmark', - 'environment', - 'result_value', - ] - - error = True - for key in mandatory_data: - if key not in item: - return 'Key "' + key + '" missing from request', error - elif key in item and item[key] == "": - return 'Value for key "' + key + '" empty in request', error - - # Check that the Environment exists - try: - e = Environment.objects.get(name=item['environment']) - error = False - return e, error - except Environment.DoesNotExist: - return "Environment %(environment)s not found" % item, error - - -def create_report_if_enough_data(rev, exe, e): - """Triggers Report creation when there are enough results""" - last_revs = Revision.objects.filter( - branch=rev.branch - ).order_by('-date')[:2] - if len(last_revs) > 1: - current_results = rev.results.filter(executable=exe, environment=e) - last_results = last_revs[1].results.filter( - executable=exe, environment=e) - # If there is are at least as many results as in the last revision, - # create new report - if len(current_results) >= len(last_results): - logger.debug("create_report_if_enough_data: About to create new report") - report, created = Report.objects.get_or_create( - executable=exe, environment=e, revision=rev - ) - report.full_clean() - report.save() - logger.debug("create_report_if_enough_data: Created new report.") - - -def save_result(data): - res, error = validate_result(data) - if error: - return res, True - else: - assert(isinstance(res, Environment)) - env = res - - p, created = Project.objects.get_or_create(name=data["project"]) - branch, created = Branch.objects.get_or_create(name=data["branch"], - project=p) - b, created = Benchmark.objects.get_or_create(name=data["benchmark"]) - - if created: - if "description" in data: - b.description = data["description"] - if "units" in data: - b.units = data["units"] - if "units_title" in data: - b.units_title = data["units_title"] - if "lessisbetter" in data: - b.lessisbetter = data["lessisbetter"] - b.full_clean() - b.save() - - try: - rev = branch.revisions.get(commitid=data['commitid']) - except Revision.DoesNotExist: - rev_date = data.get("revision_date") - # "None" (as string) can happen when we urlencode the POST data - if not rev_date or rev_date in ["", "None"]: - rev_date = datetime.today() - rev = Revision(branch=branch, project=p, commitid=data['commitid'], - date=rev_date) - try: - rev.full_clean() - except ValidationError as e: - return str(e), True - if p.repo_type not in ("N", ""): - try: - saverevisioninfo(rev) - except RuntimeError as e: - logger.warning("unable to save revision %s info: %s", rev, e, - exc_info=True) - rev.save() - - exe, created = Executable.objects.get_or_create( - name=data['executable'], - project=p - ) - - try: - r = Result.objects.get( - revision=rev, executable=exe, benchmark=b, environment=env) - except Result.DoesNotExist: - r = Result(revision=rev, executable=exe, benchmark=b, environment=env) - - r.value = data["result_value"] - if 'result_date' in data: - r.date = data["result_date"] - elif rev.date: - r.date = rev.date - else: - r.date = datetime.now() - - r.std_dev = data.get('std_dev') - r.val_min = data.get('min') - r.val_max = data.get('max') - - r.full_clean() - r.save() - - return (rev, exe, env), False - - @csrf_exempt def add_result(request): if request.method != 'POST': From 02a2039438fd658022bad5bf8e57e874481db691 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 20:09:16 +0100 Subject: [PATCH 063/226] Use require_POST instead of manually checking method --- codespeed/tests/test_views.py | 6 ++++++ codespeed/views.py | 15 +++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py index e65a9ffb..bcafaad4 100644 --- a/codespeed/tests/test_views.py +++ b/codespeed/tests/test_views.py @@ -199,6 +199,12 @@ def setUp(self): 'result_value': 458}, ] + def test_get_returns_405(self): + response = self.client.get(self.path, + {'json': json.dumps(self.data)}) + + self.assertEquals(response.status_code, 405) + def test_add_correct_results(self): """Should add all results when the request data is valid""" response = self.client.post(self.path, diff --git a/codespeed/views.py b/codespeed/views.py index 24189416..7bdd8725 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -3,10 +3,9 @@ import logging from django.core.urlresolvers import reverse -from django.http import (HttpResponse, Http404, HttpResponseNotAllowed, - HttpResponseBadRequest) +from django.http import HttpResponse, Http404, HttpResponseBadRequest from django.shortcuts import get_object_or_404, render_to_response -from django.views.decorators.http import require_GET +from django.views.decorators.http import require_GET, require_POST from django.views.decorators.csrf import csrf_exempt from django.template import RequestContext from django.conf import settings @@ -652,12 +651,9 @@ def displaylogs(request): @csrf_exempt +@require_POST def add_result(request): - if request.method != 'POST': - return HttpResponseNotAllowed('POST') - data = request.POST - - response, error = save_result(data) + response, error = save_result(request.POST) if error: logger.error("Could not save result: " + response) return HttpResponseBadRequest(response) @@ -668,9 +664,8 @@ def add_result(request): @csrf_exempt +@require_POST def add_json_results(request): - if request.method != 'POST': - return HttpResponseNotAllowed('POST') if not request.POST.get('json'): return HttpResponseBadRequest("No key 'json' in POST payload") data = json.loads(request.POST['json']) From c204d55aa7fca1596dc25a91addb9b14cfef2651 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 20:17:40 +0100 Subject: [PATCH 064/226] Add python3 future imports --- codespeed/domain.py | 2 ++ codespeed/models.py | 2 ++ codespeed/views.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/codespeed/domain.py b/codespeed/domain.py index d30d7d29..cbd5792e 100644 --- a/codespeed/domain.py +++ b/codespeed/domain.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + import logging from datetime import datetime from django.core.exceptions import ValidationError diff --git a/codespeed/models.py b/codespeed/models.py index 1e17afca..54c475c5 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + import os import json diff --git a/codespeed/views.py b/codespeed/views.py index 7bdd8725..07e4a4b1 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + import json import logging From 9893b87de02bdc1ea6256207de5cc010b095b3a5 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 20:37:37 +0100 Subject: [PATCH 065/226] Don't import unicode_literals as it breaks the comparison view Fixes #189 --- codespeed/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codespeed/domain.py b/codespeed/domain.py index cbd5792e..0abf0565 100644 --- a/codespeed/domain.py +++ b/codespeed/domain.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import import logging from datetime import datetime From 509b7d5c8b9fe98f00ca8d7bd3af63f3d90c1ba1 Mon Sep 17 00:00:00 2001 From: Miquel Torres Date: Sat, 31 Oct 2015 21:12:34 +0100 Subject: [PATCH 066/226] Upgrade jqplot to 1.0.8 --- codespeed/static/js/jqplot/README.md | 3 ++ codespeed/static/js/jqplot/excanvas.min.js | 4 ++- .../js/jqplot/jqplot.barRenderer.min.js | 17 ++-------- .../jqplot.canvasAxisLabelRenderer.min.js | 17 ++-------- .../jqplot.canvasAxisTickRenderer.min.js | 17 ++-------- .../jqplot/jqplot.canvasTextRenderer.min.js | 17 ++-------- .../jqplot/jqplot.categoryAxisRenderer.min.js | 33 ++----------------- .../static/js/jqplot/jqplot.cursor.min.js | 17 ++-------- .../js/jqplot/jqplot.dateAxisRenderer.min.js | 17 ++-------- .../js/jqplot/jqplot.highlighter.min.js | 17 ++-------- .../static/js/jqplot/jquery.jqplot.min.css | 2 +- .../static/js/jqplot/jquery.jqplot.min.js | 17 ++-------- 12 files changed, 34 insertions(+), 144 deletions(-) create mode 100644 codespeed/static/js/jqplot/README.md diff --git a/codespeed/static/js/jqplot/README.md b/codespeed/static/js/jqplot/README.md new file mode 100644 index 00000000..bf429c2e --- /dev/null +++ b/codespeed/static/js/jqplot/README.md @@ -0,0 +1,3 @@ +# jqPlot + +version: 1.0.8 diff --git a/codespeed/static/js/jqplot/excanvas.min.js b/codespeed/static/js/jqplot/excanvas.min.js index 896786ef..83373a53 100644 --- a/codespeed/static/js/jqplot/excanvas.min.js +++ b/codespeed/static/js/jqplot/excanvas.min.js @@ -1 +1,3 @@ -if(!document.createElement("canvas").getContext){(function(){var Y=Math;var q=Y.round;var o=Y.sin;var B=Y.cos;var H=Y.abs;var N=Y.sqrt;var d=10;var f=d/2;function A(){return this.context_||(this.context_=new D(this))}var v=Array.prototype.slice;function g(j,m,p){var i=v.call(arguments,2);return function(){return j.apply(m,i.concat(v.call(arguments)))}}function ad(i){return String(i).replace(/&/g,"&").replace(/"/g,""")}function R(j){if(!j.namespaces.g_vml_){j.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!j.namespaces.g_o_){j.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))}},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}function F(j){var ae,Z=1;j=String(j);if(j.charAt(0)=="#"){ae=j}else{if(/^rgb/.test(j)){var p=M(j);var ae="#",af;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){af=Math.floor(c(p[m])*255)}else{af=Number(p[m])}ae+=n[u(af,0,255)]}Z=p[3]}else{if(/^hsl/.test(j)){var p=M(j);ae=I(p);Z=p[3]}else{ae=b[j]||j}}}return{color:ae,alpha:Z}}var r={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||r.style,variant:m.fontVariant||r.variant,weight:m.fontWeight||r.weight,size:m.fontSize||r.size,family:m.fontFamily||r.family}}function w(m,j){var i={};for(var af in m){i[af]=m[af]}var ae=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ae*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ae/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=ae*(4/3)*Z}else{i.size=ae}}}}}i.size*=0.981;return i}function aa(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}function S(i){switch(i){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function D(j){this.m_=C();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=j;var i=j.ownerDocument.createElement("div");i.style.width=j.clientWidth+"px";i.style.height=j.clientHeight+"px";i.style.overflow="hidden";i.style.position="absolute";j.appendChild(i);this.element_=i;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var t=D.prototype;t.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};t.beginPath=function(){this.currentPath_=[]};t.moveTo=function(j,i){var m=this.getCoords_(j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};t.lineTo=function(j,i){var m=this.getCoords_(j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};t.bezierCurveTo=function(m,j,ai,ah,ag,ae){var i=this.getCoords_(ag,ae);var af=this.getCoords_(m,j);var Z=this.getCoords_(ai,ah);K(this,af,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}t.quadraticCurveTo=function(ag,m,j,i){var af=this.getCoords_(ag,m);var ae=this.getCoords_(j,i);var ah={x:this.currentX_+2/3*(af.x-this.currentX_),y:this.currentY_+2/3*(af.y-this.currentY_)};var Z={x:ah.x+(ae.x-this.currentX_)/3,y:ah.y+(ae.y-this.currentY_)/3};K(this,ah,Z,ae)};t.arc=function(aj,ah,ai,ae,j,m){ai*=d;var an=m?"at":"wa";var ak=aj+B(ae)*ai-f;var am=ah+o(ae)*ai-f;var i=aj+B(j)*ai-f;var al=ah+o(j)*ai-f;if(ak==i&&!m){ak+=0.125}var Z=this.getCoords_(aj,ah);var ag=this.getCoords_(ak,am);var af=this.getCoords_(i,al);this.currentPath_.push({type:an,x:Z.x,y:Z.y,radius:ai,xStart:ag.x,yStart:ag.y,xEnd:af.x,yEnd:af.y})};t.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};t.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};t.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};t.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};t.createRadialGradient=function(p,ae,m,j,Z,i){var af=new U("gradientradial");af.x0_=p;af.y0_=ae;af.r0_=m;af.x1_=j;af.y1_=Z;af.r1_=i;return af};t.drawImage=function(ao,m){var ah,af,aj,aw,am,ak,aq,ay;var ai=ao.runtimeStyle.width;var an=ao.runtimeStyle.height;ao.runtimeStyle.width="auto";ao.runtimeStyle.height="auto";var ag=ao.width;var au=ao.height;ao.runtimeStyle.width=ai;ao.runtimeStyle.height=an;if(arguments.length==3){ah=arguments[1];af=arguments[2];am=ak=0;aq=aj=ag;ay=aw=au}else{if(arguments.length==5){ah=arguments[1];af=arguments[2];aj=arguments[3];aw=arguments[4];am=ak=0;aq=ag;ay=au}else{if(arguments.length==9){am=arguments[1];ak=arguments[2];aq=arguments[3];ay=arguments[4];ah=arguments[5];af=arguments[6];aj=arguments[7];aw=arguments[8]}else{throw Error("Invalid number of arguments")}}}var ax=this.getCoords_(ah,af);var p=aq/2;var j=ay/2;var av=[];var i=10;var ae=10;av.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",av.join(""))};t.stroke=function(aj){var ah=[];var Z=false;var m=10;var ak=10;ah.push("ai.x){ai.x=j.x}if(ae.y==null||j.yai.y){ai.y=j.y}}}ah.push(' ">');if(!aj){y(this,ah)}else{G(this,ah,ae,ai)}ah.push("");this.element_.insertAdjacentHTML("beforeEnd",ah.join(""))};function y(m,ae){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ae.push("')}function G(ao,ag,aI,ap){var ah=ao.fillStyle;var az=ao.arcScaleX_;var ay=ao.arcScaleY_;var j=ap.x-aI.x;var p=ap.y-aI.y;if(ah instanceof U){var al=0;var aD={x:0,y:0};var av=0;var ak=1;if(ah.type_=="gradient"){var aj=ah.x0_/az;var m=ah.y0_/ay;var ai=ah.x1_/az;var aK=ah.y1_/ay;var aH=ao.getCoords_(aj,m);var aG=ao.getCoords_(ai,aK);var ae=aG.x-aH.x;var Z=aG.y-aH.y;al=Math.atan2(ae,Z)*180/Math.PI;if(al<0){al+=360}if(al<0.000001){al=0}}else{var aH=ao.getCoords_(ah.x0_,ah.y0_);aD={x:(aH.x-aI.x)/j,y:(aH.y-aI.y)/p};j/=az*d;p/=ay*d;var aB=Y.max(j,p);av=2*ah.r0_/aB;ak=2*ah.r1_/aB-av}var at=ah.colors_;at.sort(function(aL,i){return aL.offset-i.offset});var an=at.length;var ar=at[0].color;var aq=at[an-1].color;var ax=at[0].alpha*ao.globalAlpha;var aw=at[an-1].alpha*ao.globalAlpha;var aC=[];for(var aF=0;aF')}else{if(ah instanceof T){if(j&&p){var af=-aI.x;var aA=-aI.y;ag.push("')}}else{var aJ=F(ao.fillStyle);var au=aJ.color;var aE=aJ.alpha*ao.globalAlpha;ag.push('')}}}t.fill=function(){this.stroke(true)};t.closePath=function(){this.currentPath_.push({type:"close"})};t.getCoords_=function(p,j){var i=this.m_;return{x:d*(p*i[0][0]+j*i[1][0]+i[2][0])-f,y:d*(p*i[0][1]+j*i[1][1]+i[2][1])-f}};t.save=function(){var i={};x(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(C(),this.m_)};t.restore=function(){if(this.aStack_.length){x(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function k(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function X(j,i,p){if(!k(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}t.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];X(this,J(i,this.m_),false)};t.rotate=function(j){var p=B(j);var m=o(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];X(this,J(i,this.m_),false)};t.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];X(this,J(i,this.m_),true)};t.transform=function(Z,p,af,ae,j,i){var m=[[Z,p,0],[af,ae,0],[j,i,1]];X(this,J(m,this.m_),true)};t.setTransform=function(ae,Z,ag,af,p,j){var i=[[ae,Z,0],[ag,af,0],[p,j,1]];X(this,i,true)};t.drawText_=function(ak,ai,ah,an,ag){var am=this.m_,aq=1000,j=0,ap=aq,af={x:0,y:0},ae=[];var i=w(E(this.font),this.element_);var p=aa(i);var ar=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=ar.direction=="ltr"?"right":"left";break;case"start":Z=ar.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":af.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":af.y=-i.size/2.25;break}switch(Z){case"right":j=aq;ap=0.05;break;case"center":j=ap=aq/2;break}var ao=this.getCoords_(ai+af.x,ah+af.y);ae.push('');if(ag){y(this,ae)}else{G(this,ae,{x:-j,y:0},{x:ap,y:i.size})}var al=am[0][0].toFixed(3)+","+am[1][0].toFixed(3)+","+am[0][1].toFixed(3)+","+am[1][1].toFixed(3)+",0,0";var aj=q(ao.x/d)+","+q(ao.y/d);ae.push('','','');this.element_.insertAdjacentHTML("beforeEnd",ae.join(""))};t.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};t.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};t.measureText=function(m){if(!this.textMeasureEl_){var i='';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};t.clip=function(){};t.arcTo=function(){};t.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var W=P.prototype=new Error;W.INDEX_SIZE_ERR=1;W.DOMSTRING_SIZE_ERR=2;W.HIERARCHY_REQUEST_ERR=3;W.WRONG_DOCUMENT_ERR=4;W.INVALID_CHARACTER_ERR=5;W.NO_DATA_ALLOWED_ERR=6;W.NO_MODIFICATION_ALLOWED_ERR=7;W.NOT_FOUND_ERR=8;W.NOT_SUPPORTED_ERR=9;W.INUSE_ATTRIBUTE_ERR=10;W.INVALID_STATE_ERR=11;W.SYNTAX_ERR=12;W.INVALID_MODIFICATION_ERR=13;W.NAMESPACE_ERR=14;W.INVALID_ACCESS_ERR=15;W.VALIDATION_ERR=16;W.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()}; \ No newline at end of file +/* jqPlot @VERSION | (c) 2009-2013 Chris Leonello | jplot.com + jsDate | (c) 2010-2013 Chris Leonello + */if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&").replace(/"/g,""")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;i.family="'"+i.family.replace(/(\'|\")/g,"").replace(/\s*,\s*/g,"', '")+"'";return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(al){var aj=[];var Z=false;var m=10;var am=10;aj.push("ak.x){ak.x=j.x}if(ag.y==null||j.yak.y){ak.y=j.y}}}aj.push(' ">');if(!al){w(this,aj)}else{G(this,aj,ag,ak)}aj.push("");this.element_.insertAdjacentHTML("beforeEnd",aj.join(""))};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d+1-ao[0][0])+","+n(aq.y/d-2*ao[1][0]);ag.push('','','');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P;G_vmlCanvasManager._version=888})()}; \ No newline at end of file diff --git a/codespeed/static/js/jqplot/jqplot.barRenderer.min.js b/codespeed/static/js/jqplot/jqplot.barRenderer.min.js index f53635d5..93d7a45c 100644 --- a/codespeed/static/js/jqplot/jqplot.barRenderer.min.js +++ b/codespeed/static/js/jqplot/jqplot.barRenderer.min.js @@ -1,14 +1,3 @@ -/** - * Copyright (c) 2009 Chris Leonello - * jqPlot is currently available for use in all personal or commercial projects - * under both the MIT and GPL version 2.0 licenses. This means that you can - * choose the license that best suits your project and use it accordingly. - * - * Although not required, the author would appreciate an email letting him - * know of any substantial use of jqPlot. You can reach the author at: - * chris dot leonello at gmail dot com or see http://www.jqplot.com/info.php . - * - * If you are feeling kind and generous, consider supporting the project by - * making a donation at: http://www.jqplot.com/donate.php . - */ -(function(b){b.jqplot.BarRenderer=function(){b.jqplot.LineRenderer.call(this)};b.jqplot.BarRenderer.prototype=new b.jqplot.LineRenderer();b.jqplot.BarRenderer.prototype.constructor=b.jqplot.BarRenderer;b.jqplot.BarRenderer.prototype.init=function(d){this.barPadding=8;this.barMargin=10;this.barDirection="vertical";this.barWidth=null;this.shadowOffset=2;this.shadowDepth=5;this.shadowAlpha=0.08;this.waterfall=false;this.varyBarColor=false;b.extend(true,this,d);this.fill=true;if(this.waterfall){this.fillToZero=false;this.disableStack=true}if(this.barDirection=="vertical"){this._primaryAxis="_xaxis";this._stackAxis="y";this.fillAxis="y"}else{this._primaryAxis="_yaxis";this._stackAxis="x";this.fillAxis="x"}var e={lineJoin:"miter",lineCap:"round",fill:true,isarc:false,strokeStyle:this.color,fillStyle:this.color,closePath:this.fill};this.renderer.shapeRenderer.init(e);var c={lineJoin:"miter",lineCap:"round",fill:true,isarc:false,angle:this.shadowAngle,offset:this.shadowOffset,alpha:this.shadowAlpha,depth:this.shadowDepth,closePath:this.fill};this.renderer.shadowRenderer.init(c)};function a(h,g,f,c){if(this.rendererOptions.barDirection=="horizontal"){this._stackAxis="x";this._primaryAxis="_yaxis"}if(this.rendererOptions.waterfall==true){this._data=b.extend(true,[],this.data);var e=0;var j=(!this.rendererOptions.barDirection||this.rendererOptions.barDirection=="vertical")?1:0;for(var d=0;d0){this.data[d][j]+=this.data[d-1][j]}}this.data[this.data.length]=(j==1)?[this.data.length+1,e]:[e,this.data.length+1];this._data[this._data.length]=(j==1)?[this._data.length+1,e]:[e,this._data.length+1]}}b.jqplot.preSeriesInitHooks.push(a);b.jqplot.BarRenderer.prototype.calcSeriesNumbers=function(){var g=0;var h=0;var f=this[this._primaryAxis];var e,d,j;for(var c=0;c0&&u0&&u0){this.data[q][u]+=this.data[q-1][u]}}this.data[this.data.length]=(u==1)?[this.data.length+1,s]:[s,this.data.length+1];this._data[this._data.length]=(u==1)?[this._data.length+1,s]:[s,this._data.length+1]}if(this.rendererOptions.groups>1){this.breakOnNull=true;var n=this.data.length;var v=parseInt(n/this.rendererOptions.groups,10);var r=0;for(var q=v;q570)?n[p]*0.8:n[p]+0.3*(255-n[p]);n[p]=parseInt(n[p],10)}q.push("rgb("+n[0]+","+n[1]+","+n[2]+")")}return q}function i(v,u,s,t,o){var q=v,w=v-1,n,p,r=(o==="x")?0:1;if(q>0){p=t.series[w]._plotData[u][r];if((s*p)<0){n=i(w,u,s,t,o)}else{n=t.series[w].gridData[u][r]}}else{n=(r===0)?t.series[q]._xaxis.series_u2p(0):t.series[q]._yaxis.series_u2p(0)}return n}d.jqplot.BarRenderer.prototype.draw=function(E,L,q,G){var I;var A=d.extend({},q);var w=(A.shadow!=undefined)?A.shadow:this.shadow;var O=(A.showLine!=undefined)?A.showLine:this.showLine;var F=(A.fill!=undefined)?A.fill:this.fill;var p=this.xaxis;var J=this.yaxis;var y=this._xaxis.series_u2p;var K=this._yaxis.series_u2p;var D,C;this._dataColors=[];this._barPoints=[];if(this.barWidth==null){this.renderer.setBarWidth.call(this)}var N=this._plotSeriesInfo=this.renderer.calcSeriesNumbers.call(this);var x=N[0];var v=N[1];var s=N[2];var H=[];if(this._stack){this._barNudge=0}else{this._barNudge=(-Math.abs(v/2-0.5)+s)*(this.barWidth+this.barPadding)}if(O){var u=new d.jqplot.ColorGenerator(this.negativeSeriesColors);var B=new d.jqplot.ColorGenerator(this.seriesColors);var M=u.get(this.index);if(!this.useNegativeColors){M=A.fillStyle}var t=A.fillStyle;var r;var P;var o;if(this.barDirection=="vertical"){for(var I=0;I0&&I=0){o=this._yaxis.series_u2p(0)}else{if(this._yaxis.min>0){o=E.canvas.height}else{o=0}}}else{if(this.waterfall&&I==this.gridData.length-1){if(this._yaxis.min<=0&&this._yaxis.max>=0){o=this._yaxis.series_u2p(0)}else{if(this._yaxis.min>0){o=E.canvas.height}else{o=0}}}else{o=E.canvas.height}}}}}if((this.fillToZero&&this._plotData[I][1]<0)||(this.waterfall&&this._data[I][1]<0)){if(this.varyBarColor&&!this._stack){if(this.useNegativeColors){A.fillStyle=u.next()}else{A.fillStyle=B.next()}}else{A.fillStyle=M}}else{if(this.varyBarColor&&!this._stack){A.fillStyle=B.next()}else{A.fillStyle=t}}if(!this.fillToZero||this._plotData[I][1]>=0){H.push([r-this.barWidth/2,o]);H.push([r-this.barWidth/2,L[I][1]]);H.push([r+this.barWidth/2,L[I][1]]);H.push([r+this.barWidth/2,o])}else{H.push([r-this.barWidth/2,L[I][1]]);H.push([r-this.barWidth/2,o]);H.push([r+this.barWidth/2,o]);H.push([r+this.barWidth/2,L[I][1]])}this._barPoints.push(H);if(w&&!this._stack){var z=d.extend(true,{},A);delete z.fillStyle;this.renderer.shadowRenderer.draw(E,H,z)}var n=A.fillStyle||this.color;this._dataColors.push(n);this.renderer.shapeRenderer.draw(E,H,A)}}else{if(this.barDirection=="horizontal"){for(var I=0;I0&&I=0){P=this._xaxis.series_u2p(0)}else{if(this._xaxis.min>0){P=0}else{P=0}}}else{if(this.waterfall&&I==this.gridData.length-1){if(this._xaxis.min<=0&&this._xaxis.max>=0){P=this._xaxis.series_u2p(0)}else{if(this._xaxis.min>0){P=0}else{P=E.canvas.width}}}else{P=0}}}}}if((this.fillToZero&&this._plotData[I][0]<0)||(this.waterfall&&this._data[I][0]<0)){if(this.varyBarColor&&!this._stack){if(this.useNegativeColors){A.fillStyle=u.next()}else{A.fillStyle=B.next()}}else{A.fillStyle=M}}else{if(this.varyBarColor&&!this._stack){A.fillStyle=B.next()}else{A.fillStyle=t}}if(!this.fillToZero||this._plotData[I][0]>=0){H.push([P,r+this.barWidth/2]);H.push([P,r-this.barWidth/2]);H.push([L[I][0],r-this.barWidth/2]);H.push([L[I][0],r+this.barWidth/2])}else{H.push([L[I][0],r+this.barWidth/2]);H.push([L[I][0],r-this.barWidth/2]);H.push([P,r-this.barWidth/2]);H.push([P,r+this.barWidth/2])}this._barPoints.push(H);if(w&&!this._stack){var z=d.extend(true,{},A);delete z.fillStyle;this.renderer.shadowRenderer.draw(E,H,z)}var n=A.fillStyle||this.color;this._dataColors.push(n);this.renderer.shapeRenderer.draw(E,H,A)}}}}if(this.highlightColors.length==0){this.highlightColors=d.jqplot.computeHighlightColors(this._dataColors)}else{if(typeof(this.highlightColors)=="string"){var N=this.highlightColors;this.highlightColors=[];for(var I=0;I528||(d[0]==528&&d[1]>=16)){this._textRenderer=new a.jqplot.CanvasFontRenderer(e)}}else{if(a.browser.mozilla){var d=a.browser.version.split(".");if(d[0]>1||(d[0]==1&&d[1]>=9&&d[2]>0)){this._textRenderer=new a.jqplot.CanvasFontRenderer(e)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}};a.jqplot.CanvasAxisLabelRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisLabelRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisLabelRenderer.prototype.draw=function(c){var e=document.createElement("canvas");this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.position="absolute";this._domelem=e;this._elem=a(e);this._elem.addClass("jqplot-"+this.axis+"-label");return this._elem};a.jqplot.CanvasAxisLabelRenderer.prototype.pack=function(){if(a.browser.msie){window.G_vmlCanvasManager.init_(document);this._domelem=window.G_vmlCanvasManager.initElement(this._domelem)}var b=this._elem.get(0).getContext("2d");this._textRenderer.draw(b,this.label)}})(jQuery); \ No newline at end of file +/* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com + jsDate | (c) 2010-2013 Chris Leonello + */(function(a){a.jqplot.CanvasAxisLabelRenderer=function(b){this.angle=0;this.axis;this.show=true;this.showLabel=true;this.label="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="11pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);if(b.angle==null&&this.axis!="xaxis"&&this.axis!="x2axis"){this.angle=-90}var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisLabelRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisLabelRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisLabelRenderer.prototype.draw=function(c,f){if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css({position:"absolute"});this._elem.addClass("jqplot-"+this.axis+"-label");e=null;return this._elem};a.jqplot.CanvasAxisLabelRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery); \ No newline at end of file diff --git a/codespeed/static/js/jqplot/jqplot.canvasAxisTickRenderer.min.js b/codespeed/static/js/jqplot/jqplot.canvasAxisTickRenderer.min.js index ea02683b..19875be4 100644 --- a/codespeed/static/js/jqplot/jqplot.canvasAxisTickRenderer.min.js +++ b/codespeed/static/js/jqplot/jqplot.canvasAxisTickRenderer.min.js @@ -1,14 +1,3 @@ -/** - * Copyright (c) 2009 Chris Leonello - * jqPlot is currently available for use in all personal or commercial projects - * under both the MIT and GPL version 2.0 licenses. This means that you can - * choose the license that best suits your project and use it accordingly. - * - * Although not required, the author would appreciate an email letting him - * know of any substantial use of jqPlot. You can reach the author at: - * chris dot leonello at gmail dot com or see http://www.jqplot.com/info.php . - * - * If you are feeling kind and generous, consider supporting the project by - * making a donation at: http://www.jqplot.com/donate.php . - */ -(function(a){a.jqplot.CanvasAxisTickRenderer=function(b){this.mark="outside";this.showMark=true;this.showGridline=true;this.isMinorTick=false;this.angle=0;this.markSize=4;this.show=true;this.showLabel=true;this.labelPosition="auto";this.label="";this.value=null;this._styles={};this.formatter=a.jqplot.DefaultTickFormatter;this.formatString="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="11px";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=false;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);var e={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){e.pt2px=this.pt2px}if(this.enableFontSupport){if(a.browser.safari){var d=a.browser.version.split(".");for(var c=0;c528||(d[0]==528&&d[1]>=16)){this._textRenderer=new a.jqplot.CanvasFontRenderer(e)}}else{if(a.browser.mozilla){var d=a.browser.version.split(".");if(d[0]>1||(d[0]==1&&d[1]>=9&&d[2]>0)){this._textRenderer=new a.jqplot.CanvasFontRenderer(e)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(e)}};a.jqplot.CanvasAxisTickRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisTickRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisTickRenderer.prototype.setTick=function(b,d,c){this.value=b;if(c){this.isMinorTick=true}return this};a.jqplot.CanvasAxisTickRenderer.prototype.draw=function(c){if(!this.label){this.label=this.formatter(this.formatString,this.value)}var e=document.createElement("canvas");this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.textAlign="left";e.style.position="absolute";this._domelem=e;this._elem=a(e);this._elem.css(this._styles);this._elem.addClass("jqplot-"+this.axis+"-tick");return this._elem};a.jqplot.CanvasAxisTickRenderer.prototype.pack=function(){if(a.browser.msie){window.G_vmlCanvasManager.init_(document);this._domelem=window.G_vmlCanvasManager.initElement(this._domelem)}var b=this._elem.get(0).getContext("2d");this._textRenderer.draw(b,this.label)}})(jQuery); \ No newline at end of file +/* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com + jsDate | (c) 2010-2013 Chris Leonello + */(function(a){a.jqplot.CanvasAxisTickRenderer=function(b){this.mark="outside";this.showMark=true;this.showGridline=true;this.isMinorTick=false;this.angle=0;this.markSize=4;this.show=true;this.showLabel=true;this.labelPosition="auto";this.label="";this.value=null;this._styles={};this.formatter=a.jqplot.DefaultTickFormatter;this.formatString="";this.prefix="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="10pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisTickRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisTickRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getTop=function(b){if(this._elem){return this._elem.position().top}else{return null}};a.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisTickRenderer.prototype.setTick=function(b,d,c){this.value=b;if(c){this.isMinorTick=true}return this};a.jqplot.CanvasAxisTickRenderer.prototype.draw=function(c,f){if(!this.label){this.label=this.prefix+this.formatter(this.formatString,this.value)}if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.textAlign="left";e.style.position="absolute";e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css(this._styles);this._elem.addClass("jqplot-"+this.axis+"-tick");e=null;return this._elem};a.jqplot.CanvasAxisTickRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery); \ No newline at end of file diff --git a/codespeed/static/js/jqplot/jqplot.canvasTextRenderer.min.js b/codespeed/static/js/jqplot/jqplot.canvasTextRenderer.min.js index 8e10d118..90199dd1 100644 --- a/codespeed/static/js/jqplot/jqplot.canvasTextRenderer.min.js +++ b/codespeed/static/js/jqplot/jqplot.canvasTextRenderer.min.js @@ -1,14 +1,3 @@ -/** - * Copyright (c) 2009 Chris Leonello - * jqPlot is currently available for use in all personal or commercial projects - * under both the MIT and GPL version 2.0 licenses. This means that you can - * choose the license that best suits your project and use it accordingly. - * - * Although not required, the author would appreciate an email letting him - * know of any substantial use of jqPlot. You can reach the author at: - * chris dot leonello at gmail dot com or see http://www.jqplot.com/info.php . - * - * If you are feeling kind and generous, consider supporting the project by - * making a donation at: http://www.jqplot.com/donate.php . - */ -(function(a){a.jqplot.CanvasTextRenderer=function(b){this.fontStyle="normal";this.fontVariant="normal";this.fontWeight="normal";this.fontSize="10px";this.fontFamily="sans-serif";this.fontStretch=1;this.fillStyle="#666666";this.angle=0;this.textAlign="start";this.textBaseline="alphabetic";this.text;this.width;this.height;this.pt2px=1.28;a.extend(true,this,b);this.normalizedFontSize=this.normalizeFontSize(this.fontSize);this.setHeight()};a.jqplot.CanvasTextRenderer.prototype.init=function(b){a.extend(true,this,b);this.normalizedFontSize=this.normalizeFontSize(this.fontSize);this.setHeight()};a.jqplot.CanvasTextRenderer.prototype.normalizeFontSize=function(b){b=String(b);n=parseFloat(b);if(b.indexOf("px")>-1){return n/this.pt2px}else{if(b.indexOf("pt")>-1){return n}else{if(b.indexOf("em")>-1){return n*12}else{if(b.indexOf("%")>-1){return n*12/100}else{return n/this.pt2px}}}}};a.jqplot.CanvasTextRenderer.prototype.fontWeight2Float=function(b){if(Number(b)){return b/400}else{switch(b){case"normal":return 1;break;case"bold":return 1.75;break;case"bolder":return 2.25;break;case"lighter":return 0.75;break;default:return 1;break}}};a.jqplot.CanvasTextRenderer.prototype.getText=function(){return this.text};a.jqplot.CanvasTextRenderer.prototype.setText=function(c,b){this.text=c;this.setWidth(b);return this};a.jqplot.CanvasTextRenderer.prototype.getWidth=function(b){return this.width};a.jqplot.CanvasTextRenderer.prototype.setWidth=function(c,b){if(!b){this.width=this.measure(c,this.text)}else{this.width=b}return this};a.jqplot.CanvasTextRenderer.prototype.getHeight=function(b){return this.height};a.jqplot.CanvasTextRenderer.prototype.setHeight=function(b){if(!b){this.height=this.normalizedFontSize*this.pt2px}else{this.height=b}return this};a.jqplot.CanvasTextRenderer.prototype.letter=function(b){return this.letters[b]};a.jqplot.CanvasTextRenderer.prototype.ascent=function(){return this.normalizedFontSize};a.jqplot.CanvasTextRenderer.prototype.descent=function(){return 7*this.normalizedFontSize/25};a.jqplot.CanvasTextRenderer.prototype.measure=function(d,f){var e=0;var b=f.length;for(i=0;i30)?2:2+(30-this.normalizedFontSize)/20;t.lineWidth=u*k*this.fontWeight2Float(this.fontWeight);for(var g=0;g":{width:24,points:[[4,18],[20,9],[4,0]]},"?":{width:18,points:[[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]]},"@":{width:27,points:[[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]]},A:{width:18,points:[[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]]},B:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]]},C:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]]},D:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]]},E:{width:19,points:[[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]]},F:{width:18,points:[[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]]},G:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]]},H:{width:22,points:[[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]]},I:{width:8,points:[[4,21],[4,0]]},J:{width:16,points:[[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]]},K:{width:21,points:[[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]]},L:{width:17,points:[[4,21],[4,0],[-1,-1],[4,0],[16,0]]},M:{width:24,points:[[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]]},N:{width:22,points:[[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]]},O:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]]},P:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]]},Q:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]]},R:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]]},S:{width:20,points:[[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},T:{width:16,points:[[8,21],[8,0],[-1,-1],[1,21],[15,21]]},U:{width:22,points:[[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]]},V:{width:18,points:[[1,21],[9,0],[-1,-1],[17,21],[9,0]]},W:{width:24,points:[[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]]},X:{width:20,points:[[3,21],[17,0],[-1,-1],[17,21],[3,0]]},Y:{width:18,points:[[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]]},Z:{width:20,points:[[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]]},"[":{width:14,points:[[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]]},"\\":{width:14,points:[[0,21],[14,-3]]},"]":{width:14,points:[[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]]},"^":{width:16,points:[[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]]},_:{width:16,points:[[0,-2],[16,-2]]},"`":{width:10,points:[[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]]},a:{width:19,points:[[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},b:{width:19,points:[[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},c:{width:18,points:[[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},d:{width:19,points:[[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},e:{width:18,points:[[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},f:{width:12,points:[[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]]},g:{width:19,points:[[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},h:{width:19,points:[[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},i:{width:8,points:[[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]]},j:{width:10,points:[[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]]},k:{width:17,points:[[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]]},l:{width:8,points:[[4,21],[4,0]]},m:{width:30,points:[[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]]},n:{width:19,points:[[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},o:{width:19,points:[[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]]},p:{width:19,points:[[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},q:{width:19,points:[[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},r:{width:13,points:[[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]]},s:{width:17,points:[[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]]},t:{width:12,points:[[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]]},u:{width:19,points:[[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]]},v:{width:16,points:[[2,14],[8,0],[-1,-1],[14,14],[8,0]]},w:{width:22,points:[[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]]},x:{width:17,points:[[3,14],[14,0],[-1,-1],[14,14],[3,0]]},y:{width:16,points:[[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]]},z:{width:17,points:[[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]]},"{":{width:14,points:[[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]]},"|":{width:8,points:[[4,25],[4,-7]]},"}":{width:14,points:[[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]]},"~":{width:24,points:[[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]]}};a.jqplot.CanvasFontRenderer=function(b){b=b||{};if(!b.pt2px){b.pt2px=1.5}a.jqplot.CanvasTextRenderer.call(this,b)};a.jqplot.CanvasFontRenderer.prototype=new a.jqplot.CanvasTextRenderer({});a.jqplot.CanvasFontRenderer.prototype.constructor=a.jqplot.CanvasFontRenderer;a.jqplot.CanvasFontRenderer.prototype.measure=function(c,e){var d=this.fontSize+" "+this.fontFamily;c.save();c.font=d;var b=c.measureText(e).width;c.restore();return b};a.jqplot.CanvasFontRenderer.prototype.draw=function(e,g){var c=0;var h=this.height*0.72;e.save();var d,b;if((-Math.PI/2<=this.angle&&this.angle<=0)||(Math.PI*3/2<=this.angle&&this.angle<=Math.PI*2)){d=0;b=-Math.sin(this.angle)*this.width}else{if((0-1){return c/this.pt2px}else{if(b.indexOf("pt")>-1){return c}else{if(b.indexOf("em")>-1){return c*12}else{if(b.indexOf("%")>-1){return c*12/100}else{return c/this.pt2px}}}}};a.jqplot.CanvasTextRenderer.prototype.fontWeight2Float=function(b){if(Number(b)){return b/400}else{switch(b){case"normal":return 1;break;case"bold":return 1.75;break;case"bolder":return 2.25;break;case"lighter":return 0.75;break;default:return 1;break}}};a.jqplot.CanvasTextRenderer.prototype.getText=function(){return this.text};a.jqplot.CanvasTextRenderer.prototype.setText=function(c,b){this.text=c;this.setWidth(b);return this};a.jqplot.CanvasTextRenderer.prototype.getWidth=function(b){return this.width};a.jqplot.CanvasTextRenderer.prototype.setWidth=function(c,b){if(!b){this.width=this.measure(c,this.text)}else{this.width=b}return this};a.jqplot.CanvasTextRenderer.prototype.getHeight=function(b){return this.height};a.jqplot.CanvasTextRenderer.prototype.setHeight=function(b){if(!b){this.height=this.normalizedFontSize*this.pt2px}else{this.height=b}return this};a.jqplot.CanvasTextRenderer.prototype.letter=function(b){return this.letters[b]};a.jqplot.CanvasTextRenderer.prototype.ascent=function(){return this.normalizedFontSize};a.jqplot.CanvasTextRenderer.prototype.descent=function(){return 7*this.normalizedFontSize/25};a.jqplot.CanvasTextRenderer.prototype.measure=function(d,g){var f=0;var b=g.length;for(var e=0;e30)?2:2+(30-this.normalizedFontSize)/20;s.lineWidth=t*k*this.fontWeight2Float(this.fontWeight);for(var g=0;g":{width:24,points:[[4,18],[20,9],[4,0]]},"?":{width:18,points:[[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]]},"@":{width:27,points:[[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]]},A:{width:18,points:[[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]]},B:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]]},C:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]]},D:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]]},E:{width:19,points:[[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]]},F:{width:18,points:[[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]]},G:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]]},H:{width:22,points:[[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]]},I:{width:8,points:[[4,21],[4,0]]},J:{width:16,points:[[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]]},K:{width:21,points:[[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]]},L:{width:17,points:[[4,21],[4,0],[-1,-1],[4,0],[16,0]]},M:{width:24,points:[[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]]},N:{width:22,points:[[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]]},O:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]]},P:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]]},Q:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]]},R:{width:21,points:[[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]]},S:{width:20,points:[[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},T:{width:16,points:[[8,21],[8,0],[-1,-1],[1,21],[15,21]]},U:{width:22,points:[[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]]},V:{width:18,points:[[1,21],[9,0],[-1,-1],[17,21],[9,0]]},W:{width:24,points:[[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]]},X:{width:20,points:[[3,21],[17,0],[-1,-1],[17,21],[3,0]]},Y:{width:18,points:[[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]]},Z:{width:20,points:[[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]]},"[":{width:14,points:[[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]]},"\\":{width:14,points:[[0,21],[14,-3]]},"]":{width:14,points:[[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]]},"^":{width:16,points:[[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]]},_:{width:16,points:[[0,-2],[16,-2]]},"`":{width:10,points:[[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]]},a:{width:19,points:[[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},b:{width:19,points:[[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},c:{width:18,points:[[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},d:{width:19,points:[[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},e:{width:18,points:[[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},f:{width:12,points:[[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]]},g:{width:19,points:[[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},h:{width:19,points:[[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},i:{width:8,points:[[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]]},j:{width:10,points:[[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]]},k:{width:17,points:[[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]]},l:{width:8,points:[[4,21],[4,0]]},m:{width:30,points:[[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]]},n:{width:19,points:[[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},o:{width:19,points:[[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]]},p:{width:19,points:[[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},q:{width:19,points:[[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},r:{width:13,points:[[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]]},s:{width:17,points:[[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]]},t:{width:12,points:[[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]]},u:{width:19,points:[[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]]},v:{width:16,points:[[2,14],[8,0],[-1,-1],[14,14],[8,0]]},w:{width:22,points:[[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]]},x:{width:17,points:[[3,14],[14,0],[-1,-1],[14,14],[3,0]]},y:{width:16,points:[[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]]},z:{width:17,points:[[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]]},"{":{width:14,points:[[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]]},"|":{width:8,points:[[4,25],[4,-7]]},"}":{width:14,points:[[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]]},"~":{width:24,points:[[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]]}};a.jqplot.CanvasFontRenderer=function(b){b=b||{};if(!b.pt2px){b.pt2px=1.5}a.jqplot.CanvasTextRenderer.call(this,b)};a.jqplot.CanvasFontRenderer.prototype=new a.jqplot.CanvasTextRenderer({});a.jqplot.CanvasFontRenderer.prototype.constructor=a.jqplot.CanvasFontRenderer;a.jqplot.CanvasFontRenderer.prototype.measure=function(c,e){var d=this.fontSize+" "+this.fontFamily;c.save();c.font=d;var b=c.measureText(e).width;c.restore();return b};a.jqplot.CanvasFontRenderer.prototype.draw=function(e,g){var c=0;var h=this.height*0.72;e.save();var d,b;if((-Math.PI/2<=this.angle&&this.angle<=0)||(Math.PI*3/2<=this.angle&&this.angle<=Math.PI*2)){d=0;b=-Math.sin(this.angle)*this.width}else{if((0b.max||b.max==null){b.max=h[c][0]}}else{if(h[c][1]b.max||b.max==null){b.max=h[c][1]}}}}if(this.groupLabels.length){this.groups=this.groupLabels.length}};a.jqplot.CategoryAxisRenderer.prototype.createTicks=function(){var D=this._ticks;var z=this.ticks;var F=this.name;var C=this._dataBounds;var v,A;var q,w;var d,c;var b,x;if(z.length){if(this.groups>1&&!this._grouped){var r=z.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x1&&!this._grouped){var r=y.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x0&&o
');if(this.name=="xaxis"||this.name=="x2axis"){this._elem.width(this._plotDimensions.width)}else{this._elem.height(this._plotDimensions.height)}this.labelOptions.axis=this.name;this._label=new this.labelRenderer(this.labelOptions);if(this._label.show){var g=this._label.draw(b,j);g.appendTo(this._elem)}var f=this._ticks;for(var e=0;e');g.html(this.groupLabels[e]);this._groupLabels.push(g);g.appendTo(this._elem)}}return this._elem};a.jqplot.CategoryAxisRenderer.prototype.set=function(){var e=0;var m;var k=0;var f=0;var d=(this._label==null)?false:this._label.show;if(this.show){var n=this._ticks;for(var c=0;ce){e=m}}}var j=0;for(var c=0;cj){j=m}}if(d){k=this._label._elem.outerWidth(true);f=this._label._elem.outerHeight(true)}if(this.name=="xaxis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",bottom:"0px"})}else{if(this.name=="x2axis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",top:"0px"})}else{if(this.name=="yaxis"){e+=j+k;this._elem.css({width:e+"px",left:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}else{e+=j+k;this._elem.css({width:e+"px",right:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}}}}};a.jqplot.CategoryAxisRenderer.prototype.pack=function(e,c){var C=this._ticks;var v=this.max;var s=this.min;var n=c.max;var l=c.min;var q=(this._label==null)?false:this._label.show;var x;for(var r in e){this._elem.css(r,e[r])}this._offsets=c;var g=n-l;var k=v-s;this.p2u=function(h){return(h-l)*k/g+s};this.u2p=function(h){return(h-s)*g/k+l};if(this.name=="xaxis"||this.name=="x2axis"){this.series_u2p=function(h){return(h-s)*g/k};this.series_p2u=function(h){return h*k/g+s}}else{this.series_u2p=function(h){return(h-v)*g/k};this.series_p2u=function(h){return h*k/g+v}}if(this.show){if(this.name=="xaxis"||this.name=="x2axis"){for(x=0;x0){b=-o._textRenderer.height*Math.cos(-o._textRenderer.angle)/2}else{b=-o.getHeight()+o._textRenderer.height*Math.cos(o._textRenderer.angle)/2}break;case"middle":b=-o.getHeight()/2;break;default:b=-o.getHeight()/2;break}}else{b=-o.getHeight()/2}var D=this.u2p(o.value)+b+"px";o._elem.css("top",D);o.pack()}}var z=["left",0];if(q){var y=this._label._elem.outerHeight(true);this._label._elem.css("top",n-g/2-y/2+"px");if(this.name=="yaxis"){this._label._elem.css("left","0px");z=["left",this._label._elem.outerWidth(true)]}else{this._label._elem.css("right","0px");z=["right",this._label._elem.outerWidth(true)]}this._label.pack()}var d=parseInt(this._ticks.length/this.groups,10);for(x=0;xb.max||b.max==null){b.max=h[c][0]}}else{if(h[c][1]b.max||b.max==null){b.max=h[c][1]}}}}if(this.groupLabels.length){this.groups=this.groupLabels.length}};a.jqplot.CategoryAxisRenderer.prototype.createTicks=function(){var D=this._ticks;var z=this.ticks;var F=this.name;var C=this._dataBounds;var v,A;var q,w;var d,c;var b,x;if(z.length){if(this.groups>1&&!this._grouped){var r=z.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x1&&!this._grouped){var r=y.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x0&&o');if(this.name=="xaxis"||this.name=="x2axis"){this._elem.width(this._plotDimensions.width)}else{this._elem.height(this._plotDimensions.height)}this.labelOptions.axis=this.name;this._label=new this.labelRenderer(this.labelOptions);if(this._label.show){var g=this._label.draw(b,j);g.appendTo(this._elem)}var f=this._ticks;for(var e=0;e');g.html(this.groupLabels[e]);this._groupLabels.push(g);g.appendTo(this._elem)}}return this._elem};a.jqplot.CategoryAxisRenderer.prototype.set=function(){var e=0;var m;var k=0;var f=0;var d=(this._label==null)?false:this._label.show;if(this.show){var n=this._ticks;for(var c=0;ce){e=m}}}var j=0;for(var c=0;cj){j=m}}if(d){k=this._label._elem.outerWidth(true);f=this._label._elem.outerHeight(true)}if(this.name=="xaxis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",bottom:"0px"})}else{if(this.name=="x2axis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",top:"0px"})}else{if(this.name=="yaxis"){e+=j+k;this._elem.css({width:e+"px",left:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}else{e+=j+k;this._elem.css({width:e+"px",right:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}}}}};a.jqplot.CategoryAxisRenderer.prototype.pack=function(e,c){var C=this._ticks;var v=this.max;var s=this.min;var n=c.max;var l=c.min;var q=(this._label==null)?false:this._label.show;var x;for(var r in e){this._elem.css(r,e[r])}this._offsets=c;var g=n-l;var k=v-s;if(!this.reverse){this.u2p=function(h){return(h-s)*g/k+l};this.p2u=function(h){return(h-l)*k/g+s};if(this.name=="xaxis"||this.name=="x2axis"){this.series_u2p=function(h){return(h-s)*g/k};this.series_p2u=function(h){return h*k/g+s}}else{this.series_u2p=function(h){return(h-v)*g/k};this.series_p2u=function(h){return h*k/g+v}}}else{this.u2p=function(h){return l+(v-h)*g/k};this.p2u=function(h){return s+(h-l)*k/g};if(this.name=="xaxis"||this.name=="x2axis"){this.series_u2p=function(h){return(v-h)*g/k};this.series_p2u=function(h){return h*k/g+v}}else{this.series_u2p=function(h){return(s-h)*g/k};this.series_p2u=function(h){return h*k/g+s}}}if(this.show){if(this.name=="xaxis"||this.name=="x2axis"){for(x=0;x=this._ticks.length-1){continue}if(this._ticks[u]._elem&&this._ticks[u].label!=" "){var o=this._ticks[u]._elem;var r=o.position();B+=r.left+o.outerWidth(true)/2;f++}}B=B/f;this._groupLabels[x].css({left:(B-this._groupLabels[x].outerWidth(true)/2)});this._groupLabels[x].css(z[0],z[1])}}else{for(x=0;x0){b=-o._textRenderer.height*Math.cos(-o._textRenderer.angle)/2}else{b=-o.getHeight()+o._textRenderer.height*Math.cos(o._textRenderer.angle)/2}break;case"middle":b=-o.getHeight()/2;break;default:b=-o.getHeight()/2;break}}else{b=-o.getHeight()/2}var D=this.u2p(o.value)+b+"px";o._elem.css("top",D);o.pack()}}var z=["left",0];if(q){var y=this._label._elem.outerHeight(true);this._label._elem.css("top",n-g/2-y/2+"px");if(this.name=="yaxis"){this._label._elem.css("left","0px");z=["left",this._label._elem.outerWidth(true)]}else{this._label._elem.css("right","0px");z=["right",this._label._elem.outerWidth(true)]}this._label.pack()}var d=parseInt(this._ticks.length/this.groups,10)+1;for(x=0;x=this._ticks.length-1){continue}if(this._ticks[u]._elem&&this._ticks[u].label!=" "){var o=this._ticks[u]._elem;var r=o.position();B+=r.top+o.outerHeight()/2;f++}}B=B/f;this._groupLabels[x].css({top:B-this._groupLabels[x].outerHeight()/2});this._groupLabels[x].css(z[0],z[1])}}}}})(jQuery); \ No newline at end of file diff --git a/codespeed/static/js/jqplot/jqplot.cursor.min.js b/codespeed/static/js/jqplot/jqplot.cursor.min.js index 42156dee..51580bad 100644 --- a/codespeed/static/js/jqplot/jqplot.cursor.min.js +++ b/codespeed/static/js/jqplot/jqplot.cursor.min.js @@ -1,14 +1,3 @@ -/** - * Copyright (c) 2009 Chris Leonello - * jqPlot is currently available for use in all personal or commercial projects - * under both the MIT and GPL version 2.0 licenses. This means that you can - * choose the license that best suits your project and use it accordingly. - * - * Although not required, the author would appreciate an email letting him - * know of any substantial use of jqPlot. You can reach the author at: - * chris dot leonello at gmail dot com or see http://www.jqplot.com/info.php . - * - * If you are feeling kind and generous, consider supporting the project by - * making a donation at: http://www.jqplot.com/donate.php . - */ -(function(i){i.jqplot.Cursor=function(o){this.style="crosshair";this.previousCursor="auto";this.show=i.jqplot.config.enablePlugins;this.showTooltip=true;this.followMouse=false;this.tooltipLocation="se";this.tooltipOffset=6;this.showTooltipGridPosition=false;this.showTooltipUnitPosition=true;this.showTooltipDataPosition=false;this.tooltipFormatString="%.4P, %.4P";this.useAxesFormatters=true;this.tooltipAxisGroups=[];this.zoom=false;this.zoomProxy=false;this.zoomTarget=false;this.clickReset=false;this.dblClickReset=true;this.showVerticalLine=false;this.showHorizontalLine=false;this.constrainZoomTo="none";this.shapeRenderer=new i.jqplot.ShapeRenderer();this._zoom={start:[],end:[],started:false,zooming:false,isZoomed:false,axes:{start:{},end:{}}};this._tooltipElem;this.zoomCanvas;this.cursorCanvas;this.intersectionThreshold=2;this.showCursorLegend=false;this.cursorLegendFormatString=i.jqplot.Cursor.cursorLegendFormatString;i.extend(true,this,o)};i.jqplot.Cursor.cursorLegendFormatString="%s x:%s, y:%s";i.jqplot.Cursor.init=function(t,r,q){var o=q||{};this.plugins.cursor=new i.jqplot.Cursor(o.cursor);var u=this.plugins.cursor;if(u.show){i.jqplot.eventListenerHooks.push(["jqplotMouseEnter",b]);i.jqplot.eventListenerHooks.push(["jqplotMouseLeave",f]);i.jqplot.eventListenerHooks.push(["jqplotMouseMove",h]);if(u.showCursorLegend){q.legend=q.legend||{};q.legend.renderer=i.jqplot.CursorLegendRenderer;q.legend.formatString=this.plugins.cursor.cursorLegendFormatString;q.legend.show=true}if(u.zoom){i.jqplot.eventListenerHooks.push(["jqplotMouseDown",a]);i.jqplot.eventListenerHooks.push(["jqplotMouseUp",n]);if(u.clickReset){i.jqplot.eventListenerHooks.push(["jqplotClick",j])}if(u.dblClickReset){i.jqplot.eventListenerHooks.push(["jqplotDblClick",c])}}this.resetZoom=function(){var x=this.axes;if(!u.zoomProxy){for(var w in x){x[w].reset()}this.redraw()}else{var v=this.plugins.cursor.zoomCanvas._ctx;v.clearRect(0,0,v.canvas.width,v.canvas.height)}this.plugins.cursor._zoom.isZoomed=false;this.target.trigger("jqplotResetZoom",[this,this.plugins.cursor])};if(u.showTooltipDataPosition){u.showTooltipUnitPosition=false;u.showTooltipGridPosition=false;if(o.cursor.tooltipFormatString==undefined){u.tooltipFormatString=i.jqplot.Cursor.cursorLegendFormatString}}}};i.jqplot.Cursor.postDraw=function(){var w=this.plugins.cursor;w.zoomCanvas=new i.jqplot.GenericCanvas();this.eventCanvas._elem.before(w.zoomCanvas.createElement(this._gridPadding,"jqplot-zoom-canvas",this._plotDimensions));var v=w.zoomCanvas.setContext();w._tooltipElem=i('');w.zoomCanvas._elem.before(w._tooltipElem);if(w.showVerticalLine||w.showHorizontalLine){w.cursorCanvas=new i.jqplot.GenericCanvas();this.eventCanvas._elem.before(w.cursorCanvas.createElement(this._gridPadding,"jqplot-cursor-canvas",this._plotDimensions));var v=w.cursorCanvas.setContext()}if(w.showTooltipUnitPosition){if(w.tooltipAxisGroups.length===0){var r=this.series;var t;var o=[];for(var q=0;q6&&Math.abs(w.y-z._zoom.start[1])>6)||(z.constrainZoomTo=="x"&&Math.abs(w.x-z._zoom.start[0])>6)||(z.constrainZoomTo=="y"&&Math.abs(w.y-z._zoom.start[1])>6)){if(!x.plugins.cursor.zoomProxy){for(var o in t){if(z._zoom.axes[o]==undefined){z._zoom.axes[o]={};z._zoom.axes[o].numberTicks=y[o].numberTicks;z._zoom.axes[o].tickInterval=y[o].tickInterval;z._zoom.axes[o].daTickInterval=y[o].daTickInterval;z._zoom.axes[o].min=y[o].min;z._zoom.axes[o].max=y[o].max}if((z.constrainZoomTo=="none")||(z.constrainZoomTo=="x"&&o.charAt(0)=="x")||(z.constrainZoomTo=="y"&&o.charAt(0)=="y")){dp=t[o];if(dp!=null){if(dp>r[o]){y[o].min=r[o];y[o].max=dp}else{span=r[o]-dp;y[o].max=r[o];y[o].min=dp}y[o].tickInterval=null;y[o].daTickInterval=null;y[o]._ticks=[]}}}C.clearRect(0,0,C.canvas.width,C.canvas.height);x.redraw();z._zoom.isZoomed=true}x.target.trigger("jqplotZoom",[w,t,x,B])}};i.jqplot.preInitHooks.push(i.jqplot.Cursor.init);i.jqplot.postDrawHooks.push(i.jqplot.Cursor.postDraw);function e(D,q,A){var F=A.plugins.cursor;var v="";var J=false;if(F.showTooltipGridPosition){v=D.x+", "+D.y;J=true}if(F.showTooltipUnitPosition){var C;for(var B=0;B"}if(F.useAxesFormatters){var z=A.axes[C[0]]._ticks[0].formatter;var o=A.axes[C[1]]._ticks[0].formatter;var G=A.axes[C[0]]._ticks[0].formatString;var u=A.axes[C[1]]._ticks[0].formatString;v+=z(G,q[C[0]])+", "+o(u,q[C[1]])}else{v+=i.jqplot.sprintf(F.tooltipFormatString,q[C[0]],q[C[1]])}J=true}}if(F.showTooltipDataPosition){var t=A.series;var I=d(A,D.x,D.y);var J=false;for(var B=0;B"}v+=i.jqplot.sprintf(F.tooltipFormatString,r,y,w);J=true}}}}F._tooltipElem.html(v)}function g(C,A){var E=A.plugins.cursor;var z=E.cursorCanvas._ctx;z.clearRect(0,0,z.canvas.width,z.canvas.height);if(E.showVerticalLine){E.shapeRenderer.draw(z,[[C.x,0],[C.x,z.canvas.height]])}if(E.showHorizontalLine){E.shapeRenderer.draw(z,[[0,C.y],[z.canvas.width,C.y]])}var G=d(A,C.x,C.y);if(E.showCursorLegend){var q=i(A.targetId+" td.jqplot-cursor-legend-label");for(var B=0;By[0]){q=y[0];o=u[0]-y[0]}else{q=u[0];o=y[0]-u[0]}if(u[1]>y[1]){v=y[1];x=u[1]-y[1]}else{v=u[1];x=y[1]-u[1]}r.fillStyle="rgba(0,0,0,0.2)";r.strokeStyle="#999999";r.lineWidth=1;r.clearRect(0,0,r.canvas.width,r.canvas.height);r.fillRect(0,0,r.canvas.width,r.canvas.height);r.clearRect(q,v,o,x);r.strokeRect(q,v,o,x)}i.jqplot.CursorLegendRenderer=function(o){i.jqplot.TableLegendRenderer.call(this,o);this.formatString="%s"};i.jqplot.CursorLegendRenderer.prototype=new i.jqplot.TableLegendRenderer();i.jqplot.CursorLegendRenderer.prototype.constructor=i.jqplot.CursorLegendRenderer;i.jqplot.CursorLegendRenderer.prototype.draw=function(){if(this.show){var u=this._series;this._elem=i('
');var x=false;for(var t=0;t').appendTo(this._elem);C.data("seriesIndex",y);i('
').appendTo(C);var E=i('');E.appendTo(C);E.data("seriesIndex",y);if(this.escapeHtml){E.text(B)}else{E.html(B)}}return this._elem}})(jQuery); \ No newline at end of file +/* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com + jsDate | (c) 2010-2013 Chris Leonello + */(function(j){j.jqplot.Cursor=function(q){this.style="crosshair";this.previousCursor="auto";this.show=j.jqplot.config.enablePlugins;this.showTooltip=true;this.followMouse=false;this.tooltipLocation="se";this.tooltipOffset=6;this.showTooltipGridPosition=false;this.showTooltipUnitPosition=true;this.showTooltipDataPosition=false;this.tooltipFormatString="%.4P, %.4P";this.useAxesFormatters=true;this.tooltipAxisGroups=[];this.zoom=false;this.zoomProxy=false;this.zoomTarget=false;this.looseZoom=true;this.clickReset=false;this.dblClickReset=true;this.showVerticalLine=false;this.showHorizontalLine=false;this.constrainZoomTo="none";this.shapeRenderer=new j.jqplot.ShapeRenderer();this._zoom={start:[],end:[],started:false,zooming:false,isZoomed:false,axes:{start:{},end:{}},gridpos:{},datapos:{}};this._tooltipElem;this.zoomCanvas;this.cursorCanvas;this.intersectionThreshold=2;this.showCursorLegend=false;this.cursorLegendFormatString=j.jqplot.Cursor.cursorLegendFormatString;this._oldHandlers={onselectstart:null,ondrag:null,onmousedown:null};this.constrainOutsideZoom=true;this.showTooltipOutsideZoom=false;this.onGrid=false;j.extend(true,this,q)};j.jqplot.Cursor.cursorLegendFormatString="%s x:%s, y:%s";j.jqplot.Cursor.init=function(t,s,r){var q=r||{};this.plugins.cursor=new j.jqplot.Cursor(q.cursor);var u=this.plugins.cursor;if(u.show){j.jqplot.eventListenerHooks.push(["jqplotMouseEnter",b]);j.jqplot.eventListenerHooks.push(["jqplotMouseLeave",f]);j.jqplot.eventListenerHooks.push(["jqplotMouseMove",i]);if(u.showCursorLegend){r.legend=r.legend||{};r.legend.renderer=j.jqplot.CursorLegendRenderer;r.legend.formatString=this.plugins.cursor.cursorLegendFormatString;r.legend.show=true}if(u.zoom){j.jqplot.eventListenerHooks.push(["jqplotMouseDown",a]);if(u.clickReset){j.jqplot.eventListenerHooks.push(["jqplotClick",k])}if(u.dblClickReset){j.jqplot.eventListenerHooks.push(["jqplotDblClick",c])}}this.resetZoom=function(){var x=this.axes;if(!u.zoomProxy){for(var w in x){x[w].reset();x[w]._ticks=[];if(u._zoom.axes[w]!==undefined){x[w]._autoFormatString=u._zoom.axes[w].tickFormatString}}this.redraw()}else{var v=this.plugins.cursor.zoomCanvas._ctx;v.clearRect(0,0,v.canvas.width,v.canvas.height);v=null}this.plugins.cursor._zoom.isZoomed=false;this.target.trigger("jqplotResetZoom",[this,this.plugins.cursor])};if(u.showTooltipDataPosition){u.showTooltipUnitPosition=false;u.showTooltipGridPosition=false;if(q.cursor.tooltipFormatString==undefined){u.tooltipFormatString=j.jqplot.Cursor.cursorLegendFormatString}}}};j.jqplot.Cursor.postDraw=function(){var x=this.plugins.cursor;if(x.zoomCanvas){x.zoomCanvas.resetCanvas();x.zoomCanvas=null}if(x.cursorCanvas){x.cursorCanvas.resetCanvas();x.cursorCanvas=null}if(x._tooltipElem){x._tooltipElem.emptyForce();x._tooltipElem=null}if(x.zoom){x.zoomCanvas=new j.jqplot.GenericCanvas();this.eventCanvas._elem.before(x.zoomCanvas.createElement(this._gridPadding,"jqplot-zoom-canvas",this._plotDimensions,this));x.zoomCanvas.setContext()}var v=document.createElement("div");x._tooltipElem=j(v);v=null;x._tooltipElem.addClass("jqplot-cursor-tooltip");x._tooltipElem.css({position:"absolute",display:"none"});if(x.zoomCanvas){x.zoomCanvas._elem.before(x._tooltipElem)}else{this.eventCanvas._elem.before(x._tooltipElem)}if(x.showVerticalLine||x.showHorizontalLine){x.cursorCanvas=new j.jqplot.GenericCanvas();this.eventCanvas._elem.before(x.cursorCanvas.createElement(this._gridPadding,"jqplot-cursor-canvas",this._plotDimensions,this));x.cursorCanvas.setContext()}if(x.showTooltipUnitPosition){if(x.tooltipAxisGroups.length===0){var t=this.series;var u;var q=[];for(var r=0;r6&&Math.abs(G.y-I._zoom.start[1])>6)||(I.constrainZoomTo=="x"&&Math.abs(G.x-I._zoom.start[0])>6)||(I.constrainZoomTo=="y"&&Math.abs(G.y-I._zoom.start[1])>6)){if(!C.plugins.cursor.zoomProxy){for(var y in t){if(I._zoom.axes[y]==undefined){I._zoom.axes[y]={};I._zoom.axes[y].numberTicks=F[y].numberTicks;I._zoom.axes[y].tickInterval=F[y].tickInterval;I._zoom.axes[y].daTickInterval=F[y].daTickInterval;I._zoom.axes[y].min=F[y].min;I._zoom.axes[y].max=F[y].max;I._zoom.axes[y].tickFormatString=(F[y].tickOptions!=null)?F[y].tickOptions.formatString:""}if((I.constrainZoomTo=="none")||(I.constrainZoomTo=="x"&&y.charAt(0)=="x")||(I.constrainZoomTo=="y"&&y.charAt(0)=="y")){z=t[y];if(z!=null){if(z>w[y]){v=w[y];x=z}else{D=w[y]-z;v=z;x=w[y]}q=F[y];H=null;if(q.alignTicks){if(q.name==="x2axis"&&C.axes.xaxis.show){H=C.axes.xaxis.numberTicks}else{if(q.name.charAt(0)==="y"&&q.name!=="yaxis"&&q.name!=="yMidAxis"&&C.axes.yaxis.show){H=C.axes.yaxis.numberTicks}}}if(this.looseZoom&&(F[y].renderer.constructor===j.jqplot.LinearAxisRenderer||F[y].renderer.constructor===j.jqplot.LogAxisRenderer)){J=j.jqplot.LinearTickGenerator(v,x,q._scalefact,H);if(F[y].tickInset&&J[0]F[y].max-F[y].tickInset*F[y].tickInterval){J[1]-=J[4];J[2]-=1}if(F[y].renderer.constructor===j.jqplot.LogAxisRenderer&&J[0]"}if(J.useAxesFormatters){for(var D=0;D"}w+=j.jqplot.sprintf(J.tooltipFormatString,t,z,x);N=true}}}}J._tooltipElem.html(w)}function g(C,A){var E=A.plugins.cursor;var z=E.cursorCanvas._ctx;z.clearRect(0,0,z.canvas.width,z.canvas.height);if(E.showVerticalLine){E.shapeRenderer.draw(z,[[C.x,0],[C.x,z.canvas.height]])}if(E.showHorizontalLine){E.shapeRenderer.draw(z,[[0,C.y],[z.canvas.width,C.y]])}var G=d(A,C.x,C.y);if(E.showCursorLegend){var r=j(A.targetId+" td.jqplot-cursor-legend-label");for(var B=0;B0;r--){s=v[r-1];if(q[s].show){u[s]=q[s].series_p2u(w[s.charAt(0)])}}return{offsets:t,gridPos:w,dataPos:u}}function h(z){var x=z.data.plot;var y=x.plugins.cursor;if(y.show&&y.zoom&&y._zoom.started&&!y.zoomTarget){z.preventDefault();var B=y.zoomCanvas._ctx;var v=o(z);var w=v.gridPos;var t=v.dataPos;y._zoom.gridpos=w;y._zoom.datapos=t;y._zoom.zooming=true;var u=w.x;var s=w.y;var A=B.canvas.height;var q=B.canvas.width;if(y.showTooltip&&!y.onGrid&&y.showTooltipOutsideZoom){e(w,t,x);if(y.followMouse){n(w,x)}}if(y.constrainZoomTo=="x"){y._zoom.end=[u,A]}else{if(y.constrainZoomTo=="y"){y._zoom.end=[q,s]}else{y._zoom.end=[u,s]}}var r=window.getSelection;if(document.selection&&document.selection.empty){document.selection.empty()}else{if(r&&!r().isCollapsed){r().collapse()}}l.call(y);B=null}}function a(w,s,r,x,t){var v=t.plugins.cursor;if(t.plugins.mobile){j(document).one("vmouseup.jqplot_cursor",{plot:t},p)}else{j(document).one("mouseup.jqplot_cursor",{plot:t},p)}var u=t.axes;if(document.onselectstart!=undefined){v._oldHandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!=undefined){v._oldHandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}if(document.onmousedown!=undefined){v._oldHandlers.onmousedown=document.onmousedown;document.onmousedown=function(){return false}}if(v.zoom){if(!v.zoomProxy){var y=v.zoomCanvas._ctx;y.clearRect(0,0,y.canvas.width,y.canvas.height);y=null}if(v.constrainZoomTo=="x"){v._zoom.start=[s.x,0]}else{if(v.constrainZoomTo=="y"){v._zoom.start=[0,s.y]}else{v._zoom.start=[s.x,s.y]}}v._zoom.started=true;for(var q in r){v._zoom.axes.start[q]=r[q]}if(t.plugins.mobile){j(document).bind("vmousemove.jqplotCursor",{plot:t},h)}else{j(document).bind("mousemove.jqplotCursor",{plot:t},h)}}}function p(y){var v=y.data.plot;var x=v.plugins.cursor;if(x.zoom&&x._zoom.zooming&&!x.zoomTarget){var u=x._zoom.gridpos.x;var r=x._zoom.gridpos.y;var t=x._zoom.datapos;var z=x.zoomCanvas._ctx.canvas.height;var q=x.zoomCanvas._ctx.canvas.width;var w=v.axes;if(x.constrainOutsideZoom&&!x.onGrid){if(u<0){u=0}else{if(u>q){u=q}}if(r<0){r=0}else{if(r>z){r=z}}for(var s in t){if(t[s]){if(s.charAt(0)=="x"){t[s]=w[s].series_p2u(u)}else{t[s]=w[s].series_p2u(r)}}}}if(x.constrainZoomTo=="x"){r=z}else{if(x.constrainZoomTo=="y"){u=q}}x._zoom.end=[u,r];x._zoom.gridpos={x:u,y:r};x.doZoom(x._zoom.gridpos,t,v,x)}x._zoom.started=false;x._zoom.zooming=false;j(document).unbind("mousemove.jqplotCursor",h);if(document.onselectstart!=undefined&&x._oldHandlers.onselectstart!=null){document.onselectstart=x._oldHandlers.onselectstart;x._oldHandlers.onselectstart=null}if(document.ondrag!=undefined&&x._oldHandlers.ondrag!=null){document.ondrag=x._oldHandlers.ondrag;x._oldHandlers.ondrag=null}if(document.onmousedown!=undefined&&x._oldHandlers.onmousedown!=null){document.onmousedown=x._oldHandlers.onmousedown;x._oldHandlers.onmousedown=null}}function l(){var y=this._zoom.start;var u=this._zoom.end;var s=this.zoomCanvas._ctx;var r,v,x,q;if(u[0]>y[0]){r=y[0];q=u[0]-y[0]}else{r=u[0];q=y[0]-u[0]}if(u[1]>y[1]){v=y[1];x=u[1]-y[1]}else{v=u[1];x=y[1]-u[1]}s.fillStyle="rgba(0,0,0,0.2)";s.strokeStyle="#999999";s.lineWidth=1;s.clearRect(0,0,s.canvas.width,s.canvas.height);s.fillRect(0,0,s.canvas.width,s.canvas.height);s.clearRect(r,v,q,x);s.strokeRect(r,v,q,x);s=null}j.jqplot.CursorLegendRenderer=function(q){j.jqplot.TableLegendRenderer.call(this,q);this.formatString="%s"};j.jqplot.CursorLegendRenderer.prototype=new j.jqplot.TableLegendRenderer();j.jqplot.CursorLegendRenderer.prototype.constructor=j.jqplot.CursorLegendRenderer;j.jqplot.CursorLegendRenderer.prototype.draw=function(){if(this._elem){this._elem.emptyForce();this._elem=null}if(this.show){var w=this._series,A;var r=document.createElement("table");this._elem=j(r);r=null;this._elem.addClass("jqplot-legend jqplot-cursor-legend");this._elem.css("position","absolute");var q=false;for(var x=0;x').appendTo(this._elem);E.data("seriesIndex",s);j('
').appendTo(E);var G=j('');G.appendTo(E);G.data("seriesIndex",s);if(this.escapeHtml){G.text(D)}else{G.html(D)}E=null;G=null}return this._elem}})(jQuery); \ No newline at end of file diff --git a/codespeed/static/js/jqplot/jqplot.dateAxisRenderer.min.js b/codespeed/static/js/jqplot/jqplot.dateAxisRenderer.min.js index 8d1013b3..74178015 100644 --- a/codespeed/static/js/jqplot/jqplot.dateAxisRenderer.min.js +++ b/codespeed/static/js/jqplot/jqplot.dateAxisRenderer.min.js @@ -1,14 +1,3 @@ -/** - * Copyright (c) 2009 Chris Leonello - * jqPlot is currently available for use in all personal or commercial projects - * under both the MIT and GPL version 2.0 licenses. This means that you can - * choose the license that best suits your project and use it accordingly. - * - * Although not required, the author would appreciate an email letting him - * know of any substantial use of jqPlot. You can reach the author at: - * chris dot leonello at gmail dot com or see http://www.jqplot.com/info.php . - * - * If you are feeling kind and generous, consider supporting the project by - * making a donation at: http://www.jqplot.com/donate.php . - */ -(function(a){a.jqplot.DateAxisRenderer=function(){a.jqplot.LinearAxisRenderer.call(this)};a.jqplot.DateAxisRenderer.prototype=new a.jqplot.LinearAxisRenderer();a.jqplot.DateAxisRenderer.prototype.constructor=a.jqplot.DateAxisRenderer;a.jqplot.DateTickFormatter=function(b,c){if(!b){b="%Y/%m/%d"}return Date.create(c).strftime(b)};a.jqplot.DateAxisRenderer.prototype.init=function(f){this.tickOptions.formatter=a.jqplot.DateTickFormatter;this.daTickInterval=null;this._daTickInterval=null;a.extend(true,this,f);var c=this._dataBounds;for(var g=0;gc.max||c.max==null){c.max=l[e][0]}}else{l[e][1]=Date.create(l[e][1]).getTime();b[e][1]=Date.create(l[e][1]).getTime();k[e][1]=Date.create(l[e][1]).getTime();if(l[e][1]c.max||c.max==null){c.max=l[e][1]}}}}};a.jqplot.DateAxisRenderer.prototype.reset=function(){this.min=this._min;this.max=this._max;this.tickInterval=this._tickInterval;this.numberTicks=this._numberTicks;this.daTickInterval=this._daTickInterval};a.jqplot.DateAxisRenderer.prototype.createTicks=function(){var v=this._ticks;var r=this.ticks;var w=this.name;var u=this._dataBounds;var o,s;var m,p;var d,c;var b,q;if(r.length){for(q=0;q200){this.numberTicks=parseInt(3+(o-200)/100,10)}else{this.numberTicks=2}}}if(this.daTickInterval==null){this.daTickInterval=[j/(this.numberTicks-1)/1000,"seconds"]}for(var q=0;qC.max)||C.max==null){C.max=y[r][0]}if(r>0){o=Math.abs(y[r][0]-y[r-1][0]);u.intervals.push(o);if(u.frequencies.hasOwnProperty(o)){u.frequencies[o]+=1}else{u.frequencies[o]=1}}x+=o}else{y[r][1]=new h.jsDate(y[r][1]).getTime();A[r][1]=new h.jsDate(y[r][1]).getTime();z[r][1]=new h.jsDate(y[r][1]).getTime();if((y[r][1]!=null&&y[r][1]C.max)||C.max==null){C.max=y[r][1]}if(r>0){o=Math.abs(y[r][1]-y[r-1][1]);u.intervals.push(o);if(u.frequencies.hasOwnProperty(o)){u.frequencies[o]+=1}else{u.frequencies[o]=1}}}x+=o}if(D.renderer.bands){if(D.renderer.bands.hiData.length){var w=D.renderer.bands.hiData;for(var r=0,q=w.length;rC.max)||C.max==null){C.max=w[r][0]}}else{w[r][1]=new h.jsDate(w[r][1]).getTime();if((w[r][1]!=null&&w[r][1]>C.max)||C.max==null){C.max=w[r][1]}}}}if(D.renderer.bands.lowData.length){var w=D.renderer.bands.lowData;for(var r=0,q=w.length;r6){D=6}}var V=new h.jsDate(ae).setDate(1).setHours(0,0,0,0);var q=new h.jsDate(J);var z=new h.jsDate(J).setDate(1).setHours(0,0,0,0);if(q.getTime()!==z.getTime()){z=z.add(1,"month")}var S=z.diff(V,"month");ab=Math.ceil(S/D)+1;this.min=V.getTime();this.max=V.clone().add((ab-1)*D,"month").getTime();this.numberTicks=ab;for(var aa=0;aa200){this.numberTicks=parseInt(3+(n-200)/100,10)}else{this.numberTicks=2}}}O=B/(this.numberTicks-1)/1000;if(this.daTickInterval==null){this.daTickInterval=[O,"seconds"]}for(var aa=0;aa