Skip to content
Snippets Groups Projects
Commit c0724d3c authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'feature/instance-settings-api' into 'develop'

Fix #8: instance settings and python/js raven configuration

Closes #8

See merge request funkwhale/funkwhale!44
parents 0944ef2a 358b3eb0
No related branches found
No related tags found
No related merge requests found
Showing
with 308 additions and 2 deletions
BACKEND_URL=http://localhost:6001
API_AUTHENTICATION_REQUIRED=True
CACHALOT_ENABLED=False
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
......@@ -8,6 +8,11 @@ Changelog
- Front: added some unittests for the store (#55)
- Front: fixed broken login redirection when 401
- Front: Removed autoplay on page reload
- API: Added a /instance/settings endpoint
- Front: load /instance/settings on page load
- Added settings to report JS and Python error to a Sentry instance
This is disabled by default, but feel free to enable it if you want
to help us by sending your error reports :) (#8)
0.3.5 (2018-01-07)
......
from rest_framework import routers
from django.conf.urls import include, url
from funkwhale_api.instance import views as instance_views
from funkwhale_api.music import views
from funkwhale_api.playlists import views as playlists_views
from rest_framework_jwt import views as jwt_views
......@@ -25,6 +26,10 @@ router.register(
v1_patterns = router.urls
v1_patterns += [
url(r'^instance/',
include(
('funkwhale_api.instance.urls', 'instance'),
namespace='instance')),
url(r'^providers/',
include(
('funkwhale_api.providers.urls', 'providers'),
......
......@@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals
import os
import environ
from funkwhale_api import __version__
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
APPS_DIR = ROOT_DIR.path('funkwhale_api')
......@@ -56,10 +57,28 @@ THIRD_PARTY_APPS = (
'django_filters',
)
# Sentry
RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
RAVEN_DSN = env("RAVEN_DSN", default='')
if RAVEN_ENABLED:
RAVEN_CONFIG = {
'dsn': RAVEN_DSN,
# If you are using git, you can also automatically configure the
# release based on the git info.
'release': __version__,
}
THIRD_PARTY_APPS += (
'raven.contrib.django.raven_compat',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'funkwhale_api.users', # custom users app
# Your stuff: custom apps go here
'funkwhale_api.instance',
'funkwhale_api.music',
'funkwhale_api.favorites',
'funkwhale_api.radios',
......@@ -71,6 +90,7 @@ LOCAL_APPS = (
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION
......
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
raven = types.Section('raven')
@global_preferences_registry.register
class RavenDSN(types.StringPreference):
show_in_api = True
section = raven
name = 'front_dsn'
default = 'https://9e0562d46b09442bb8f6844e50cbca2b@sentry.eliotberriot.com/4'
verbose_name = (
'A raven DSN key used to report front-ent errors to '
'a sentry instance'
)
help_text = (
'Keeping the default one will report errors to funkwhale developers'
)
SENTRY_HELP_TEXT = (
'Error reporting is disabled by default but you can enable it if'
' you want to help us improve funkwhale'
)
@global_preferences_registry.register
class RavenEnabled(types.BooleanPreference):
show_in_api = True
section = raven
name = 'front_enabled'
default = False
verbose_name = (
'Wether error reporting to a Sentry instance using raven is enabled'
' for front-end errors'
)
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
]
from rest_framework import views
from rest_framework.response import Response
from dynamic_preferences.api import serializers
from dynamic_preferences.registries import global_preferences_registry
class InstanceSettings(views.APIView):
permission_classes = []
authentication_classes = []
def get(self, request, *args, **kwargs):
manager = global_preferences_registry.manager()
manager.all()
all_preferences = manager.model.objects.all().order_by(
'section', 'name'
)
api_preferences = [
p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)
]
data = serializers.GlobalPreferenceSerializer(
api_preferences, many=True).data
return Response(data, status=200)
......@@ -56,3 +56,4 @@ git+https://github.com/EliotBerriot/django-cachalot.git@django-2
django-dynamic-preferences>=1.5,<1.6
pyacoustid>=1.1.5,<1.2
raven>=6.5,<7
......@@ -3,6 +3,7 @@ import shutil
import pytest
from django.core.cache import cache as django_cache
from dynamic_preferences.registries import global_preferences_registry
from rest_framework.test import APIClient
from funkwhale_api.taskapp import celery
......@@ -29,7 +30,9 @@ def factories(db):
@pytest.fixture
def preferences(db):
yield global_preferences_registry.manager()
manager = global_preferences_registry.manager()
manager.all()
yield manager
@pytest.fixture
......@@ -48,6 +51,11 @@ def logged_in_client(db, factories, client):
delattr(client, 'user')
@pytest.fixture
def api_client(client):
return APIClient()
@pytest.fixture
def superuser_client(db, factories, client):
user = factories['users.SuperUser']()
......
from django.urls import reverse
from dynamic_preferences.api import serializers
def test_can_list_settings_via_api(preferences, api_client):
url = reverse('api:v1:instance:settings')
all_preferences = preferences.model.objects.all()
expected_preferences = {
p.preference.identifier(): p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)}
assert len(expected_preferences) > 0
response = api_client.get(url)
assert response.status_code == 200
assert len(response.data) == len(expected_preferences)
for p in response.data:
i = '__'.join([p['section'], p['name']])
assert i in expected_preferences
......@@ -78,3 +78,10 @@ API_AUTHENTICATION_REQUIRED=True
# public: anybody can register an account
# disabled: nobody can register an account
REGISTRATION_MODE=disabled
# Sentry/Raven error reporting (server side)
# Enable Raven if you want to help improve funkwhale by
# automatically sending error reports our Sentry instance.
# This will help us detect and correct bugs
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
......@@ -21,6 +21,7 @@
"jwt-decode": "^2.2.0",
"lodash": "^4.17.4",
"moxios": "^0.4.0",
"raven-js": "^3.22.3",
"semantic-ui-css": "^2.2.10",
"vue": "^2.3.3",
"vue-lazyload": "^1.1.4",
......
......@@ -22,15 +22,26 @@
</div>
</div>
</div>
<raven
v-if="$store.state.instance.settings.raven.front_enabled.value"
:dsn="$store.state.instance.settings.raven.front_dsn.value">
</raven>
</div>
</template>
<script>
import Sidebar from '@/components/Sidebar'
import Raven from '@/components/Raven'
export default {
name: 'app',
components: { Sidebar }
components: {
Sidebar,
Raven
},
created () {
this.$store.dispatch('instance/fetchSettings')
}
}
</script>
......
<template>
<div class="raven"></div>
</template>
<script>
import Raven from 'raven-js'
import RavenVue from 'raven-js/plugins/vue'
import Vue from 'vue'
import logger from '@/logging'
export default {
props: ['dsn'],
created () {
Raven.uninstall()
this.setUp()
},
destroyed () {
Raven.uninstall()
},
methods: {
setUp () {
Raven.uninstall()
logger.default.info('Installing raven...')
Raven.config(this.dsn).addPlugin(RavenVue, Vue).install()
console.log({}.test.test)
}
},
watch: {
dsn: function () {
this.setUp()
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped >
.raven {
display: none;
}
</style>
......@@ -4,6 +4,7 @@ import createPersistedState from 'vuex-persistedstate'
import favorites from './favorites'
import auth from './auth'
import instance from './instance'
import queue from './queue'
import radios from './radios'
import player from './player'
......@@ -14,6 +15,7 @@ export default new Vuex.Store({
modules: {
auth,
favorites,
instance,
queue,
radios,
player
......
import axios from 'axios'
import logger from '@/logging'
import _ from 'lodash'
export default {
namespaced: true,
state: {
settings: {
raven: {
front_enabled: {
value: false
},
front_dsn: {
value: null
}
}
}
},
mutations: {
settings: (state, value) => {
_.merge(state.settings, value)
}
},
actions: {
// Send a request to the login URL and save the returned JWT
fetchSettings ({commit}) {
return axios.get('instance/settings/').then(response => {
logger.default.info('Successfully fetched instance settings')
let sections = {}
response.data.forEach(e => {
sections[e.section] = {}
})
response.data.forEach(e => {
sections[e.section][e.name] = e
})
commit('settings', sections)
}, response => {
logger.default.error('Error while fetching settings', response.data)
})
}
}
}
var sinon = require('sinon')
import moxios from 'moxios'
import store from '@/store/instance'
import { testAction } from '../../utils'
describe('store/instance', () => {
var sandbox
beforeEach(function () {
sandbox = sinon.sandbox.create()
moxios.install()
})
afterEach(function () {
sandbox.restore()
moxios.uninstall()
})
describe('mutations', () => {
it('settings', () => {
const state = {settings: {raven: {front_dsn: {value: 'test'}}}}
let settings = {raven: {front_enabled: {value: true}}}
store.mutations.settings(state, settings)
expect(state.settings).to.deep.equal({
raven: {front_dsn: {value: 'test'}, front_enabled: {value: true}}
})
})
})
describe('actions', () => {
it('fetchSettings', (done) => {
moxios.stubRequest('instance/settings/', {
status: 200,
response: [
{
section: 'raven',
name: 'front_dsn',
value: 'test'
},
{
section: 'raven',
name: 'front_enabled',
value: false
}
]
})
testAction({
action: store.actions.fetchSettings,
payload: null,
expectedMutations: [
{
type: 'settings',
payload: {
raven: {
front_dsn: {
section: 'raven',
name: 'front_dsn',
value: 'test'
},
front_enabled: {
section: 'raven',
name: 'front_enabled',
value: false
}
}
}
}
]
}, done)
})
})
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment