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
This commit is contained in:
commit
c0724d3cb4
2
.env.dev
2
.env.dev
|
@ -1,3 +1,5 @@
|
|||
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)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
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'
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
|
||||
]
|
|
@ -0,0 +1,25 @@
|
|||
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']()
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
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>
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<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
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
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)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue