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
|
BACKEND_URL=http://localhost:6001
|
||||||
API_AUTHENTICATION_REQUIRED=True
|
API_AUTHENTICATION_REQUIRED=True
|
||||||
CACHALOT_ENABLED=False
|
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: added some unittests for the store (#55)
|
||||||
- Front: fixed broken login redirection when 401
|
- Front: fixed broken login redirection when 401
|
||||||
- Front: Removed autoplay on page reload
|
- 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)
|
0.3.5 (2018-01-07)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from django.conf.urls import include, url
|
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.music import views
|
||||||
from funkwhale_api.playlists import views as playlists_views
|
from funkwhale_api.playlists import views as playlists_views
|
||||||
from rest_framework_jwt import views as jwt_views
|
from rest_framework_jwt import views as jwt_views
|
||||||
|
@ -25,6 +26,10 @@ router.register(
|
||||||
v1_patterns = router.urls
|
v1_patterns = router.urls
|
||||||
|
|
||||||
v1_patterns += [
|
v1_patterns += [
|
||||||
|
url(r'^instance/',
|
||||||
|
include(
|
||||||
|
('funkwhale_api.instance.urls', 'instance'),
|
||||||
|
namespace='instance')),
|
||||||
url(r'^providers/',
|
url(r'^providers/',
|
||||||
include(
|
include(
|
||||||
('funkwhale_api.providers.urls', 'providers'),
|
('funkwhale_api.providers.urls', 'providers'),
|
||||||
|
|
|
@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import environ
|
import environ
|
||||||
|
from funkwhale_api import __version__
|
||||||
|
|
||||||
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
|
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
|
||||||
APPS_DIR = ROOT_DIR.path('funkwhale_api')
|
APPS_DIR = ROOT_DIR.path('funkwhale_api')
|
||||||
|
@ -56,10 +57,28 @@ THIRD_PARTY_APPS = (
|
||||||
'django_filters',
|
'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.
|
# Apps specific for this project go here.
|
||||||
LOCAL_APPS = (
|
LOCAL_APPS = (
|
||||||
'funkwhale_api.users', # custom users app
|
'funkwhale_api.users', # custom users app
|
||||||
# Your stuff: custom apps go here
|
# Your stuff: custom apps go here
|
||||||
|
'funkwhale_api.instance',
|
||||||
'funkwhale_api.music',
|
'funkwhale_api.music',
|
||||||
'funkwhale_api.favorites',
|
'funkwhale_api.favorites',
|
||||||
'funkwhale_api.radios',
|
'funkwhale_api.radios',
|
||||||
|
@ -71,6 +90,7 @@ LOCAL_APPS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
|
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
||||||
# MIDDLEWARE CONFIGURATION
|
# 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
|
django-dynamic-preferences>=1.5,<1.6
|
||||||
pyacoustid>=1.1.5,<1.2
|
pyacoustid>=1.1.5,<1.2
|
||||||
|
raven>=6.5,<7
|
||||||
|
|
|
@ -3,6 +3,7 @@ import shutil
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.cache import cache as django_cache
|
from django.core.cache import cache as django_cache
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
from funkwhale_api.taskapp import celery
|
from funkwhale_api.taskapp import celery
|
||||||
|
|
||||||
|
@ -29,7 +30,9 @@ def factories(db):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def preferences(db):
|
def preferences(db):
|
||||||
yield global_preferences_registry.manager()
|
manager = global_preferences_registry.manager()
|
||||||
|
manager.all()
|
||||||
|
yield manager
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -48,6 +51,11 @@ def logged_in_client(db, factories, client):
|
||||||
delattr(client, 'user')
|
delattr(client, 'user')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(client):
|
||||||
|
return APIClient()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def superuser_client(db, factories, client):
|
def superuser_client(db, factories, client):
|
||||||
user = factories['users.SuperUser']()
|
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
|
# public: anybody can register an account
|
||||||
# disabled: nobody can register an account
|
# disabled: nobody can register an account
|
||||||
REGISTRATION_MODE=disabled
|
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",
|
"jwt-decode": "^2.2.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"moxios": "^0.4.0",
|
"moxios": "^0.4.0",
|
||||||
|
"raven-js": "^3.22.3",
|
||||||
"semantic-ui-css": "^2.2.10",
|
"semantic-ui-css": "^2.2.10",
|
||||||
"vue": "^2.3.3",
|
"vue": "^2.3.3",
|
||||||
"vue-lazyload": "^1.1.4",
|
"vue-lazyload": "^1.1.4",
|
||||||
|
|
|
@ -22,15 +22,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Sidebar from '@/components/Sidebar'
|
import Sidebar from '@/components/Sidebar'
|
||||||
|
import Raven from '@/components/Raven'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: { Sidebar }
|
components: {
|
||||||
|
Sidebar,
|
||||||
|
Raven
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('instance/fetchSettings')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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 favorites from './favorites'
|
||||||
import auth from './auth'
|
import auth from './auth'
|
||||||
|
import instance from './instance'
|
||||||
import queue from './queue'
|
import queue from './queue'
|
||||||
import radios from './radios'
|
import radios from './radios'
|
||||||
import player from './player'
|
import player from './player'
|
||||||
|
@ -14,6 +15,7 @@ export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
auth,
|
auth,
|
||||||
favorites,
|
favorites,
|
||||||
|
instance,
|
||||||
queue,
|
queue,
|
||||||
radios,
|
radios,
|
||||||
player
|
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