From 1f8b54bf063a0d64a528014f9b5d6342f67d157c Mon Sep 17 00:00:00 2001 From: Alec Clowes Date: Sat, 16 Sep 2017 08:51:46 -0700 Subject: [PATCH 1/3] support running in anonymous readonly mode --- frontend/src/App.js | 8 ++++---- frontend/src/WorkflowDetailForm.js | 6 ++++-- frontend/src/tests/__snapshots__/App.test.js.snap | 3 +-- yawn/user/views.py | 9 ++++++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index 2772558..549d4b6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -9,7 +9,7 @@ import {YawnNavBar, YawnNavItem} from './utilities'; export default class App extends React.Component { constructor(props) { super(props); - this.state = {user: null, error: null, reload: false}; + this.state = {user: {}, error: null, reload: false}; } componentDidMount() { @@ -36,7 +36,7 @@ export default class App extends React.Component { logout = (event) => { event.preventDefault(); API.delete(`/api/users/logout/`, {}, (payload, error) => { - this.setState({user: null, error}); + this.setState({user: {}, error}); this.props.router.push('/login'); }); }; @@ -48,7 +48,7 @@ export default class App extends React.Component { }; renderToolbar() { - const user = this.state.user && `(${this.state.user.username})`; + const userAction = this.state.user.id ? `Logout (${this.state.user.username})` : 'Login'; return (
) diff --git a/frontend/src/WorkflowDetailForm.js b/frontend/src/WorkflowDetailForm.js index 1638fbe..7abb00b 100644 --- a/frontend/src/WorkflowDetailForm.js +++ b/frontend/src/WorkflowDetailForm.js @@ -73,7 +73,9 @@ export default class WorkflowDetailForm extends React.Component { handleSubmit = (event) => { const update = formValues(this.state); API.patch(`/api/workflows/${this.props.workflow.id}/`, update, (payload, error) => { - this.setState({...formValues(payload), error, editable: false}); + // if there is an error, don't parse the payload + const form = error ? {} : formValues(payload); + this.setState({...form, error, editable: false}); }); event.preventDefault(); }; @@ -131,4 +133,4 @@ export default class WorkflowDetailForm extends React.Component { ) } } -} \ No newline at end of file +} diff --git a/frontend/src/tests/__snapshots__/App.test.js.snap b/frontend/src/tests/__snapshots__/App.test.js.snap index 9260ef2..be113e0 100644 --- a/frontend/src/tests/__snapshots__/App.test.js.snap +++ b/frontend/src/tests/__snapshots__/App.test.js.snap @@ -112,8 +112,7 @@ exports[`homepage renders 1`] = ` onClick={[Function]} role="button" > - Logout - (test user) + Login diff --git a/yawn/user/views.py b/yawn/user/views.py index 68927b0..42e6a1c 100644 --- a/yawn/user/views.py +++ b/yawn/user/views.py @@ -4,7 +4,9 @@ from rest_framework.decorators import list_route from rest_framework.response import Response from rest_framework.permissions import AllowAny +from rest_framework.settings import api_settings +from yawn.user.permissions import ModelPermissions from .serializers import UserSerializer, LoginSerializer @@ -15,12 +17,17 @@ class UserViewSet(viewsets.GenericViewSet, viewsets.mixins.UpdateModelMixin): """ User endpoint, GET(list, detail), PATCH to change + + This ViewSet has `permission_classes` set and `/me/` has the default permissions + so that the default permission class can be set to IsAuthenticatedOrReadOnly + without leaking user API tokens. """ queryset = User.objects.all().order_by('id') serializer_class = UserSerializer + permission_classes = (ModelPermissions,) - @list_route(methods=['get']) + @list_route(methods=['get'], permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES) def me(self, request): serializer = self.get_serializer(request.user) return Response(serializer.data) From fcb5a3efb07bbfaca286b46d2da6012f4cd63be1 Mon Sep 17 00:00:00 2001 From: Alec Clowes Date: Sat, 16 Sep 2017 09:06:01 -0700 Subject: [PATCH 2/3] only run the worker loop once per second --- yawn/worker/main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/yawn/worker/main.py b/yawn/worker/main.py index f10ba54..f5c67ba 100644 --- a/yawn/worker/main.py +++ b/yawn/worker/main.py @@ -3,6 +3,7 @@ import typing # NOQA import collections +import time from django.db import transaction from django.db.models import functions @@ -43,14 +44,16 @@ def run(self): Main event loop. Why not threads/gevent/asyncio? - Because this is simple and maybe even more efficient! + Because this is simple and maybe more efficient """ self.handle_signals() self.executor = Manager() self.state = State.running + loop_start = time.time() logger.warning('Starting YAWN worker with concurrency=%s', self.concurrency) while True: + if self.state == State.running: # update own status and check for lost workers @@ -72,6 +75,11 @@ def run(self): self.results.extend(self.executor.read_output()) self.save_results() + # don't run the loop more than once per second + loop_duration = time.time() - loop_start + time.sleep(max(1 - loop_duration, 0)) + loop_start += loop_duration + logger.warning('Exiting') self.worker.status = Worker.EXITED self.worker.save() From 7b7805d620faba3c45d85be42ba8bd832476e04a Mon Sep 17 00:00:00 2001 From: Alec Clowes Date: Sat, 16 Sep 2017 09:20:20 -0700 Subject: [PATCH 3/3] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a7d4e32..ec3b8f9 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.1.6', + version='0.1.7', description='Yet Another Workflow Engine, a subprocess-based DAG execution system', long_description=long_description,