From 2494245b1262bd2af1fd8261c21f73047ca87353 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 20 Jun 2019 14:51:09 +0200 Subject: [PATCH 1/7] Add survey.js demo implementation to frontend --- client/index.html | 19 ++++++++------ client/package-lock.json | 49 ++++++++++++++----------------------- client/package.json | 1 + client/src/main.js | 7 +++--- client/src/pages/survey.vue | 37 ++++++++++++++++++++++++++++ client/src/router/index.js | 7 ++++-- 6 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 client/src/pages/survey.vue diff --git a/client/index.html b/client/index.html index b94913b0..45c9fa05 100644 --- a/client/index.html +++ b/client/index.html @@ -1,13 +1,16 @@ - - - skillbox + + + skillbox + + + + + + - - - diff --git a/client/src/router/index.js b/client/src/router/index.js index 1b2ba6a8..6b9b54b3 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -24,6 +24,7 @@ import activity from '@/pages/activity' import Router from 'vue-router' import editProject from '@/pages/editProject' import newProject from '@/pages/newProject' +import surveyPage from '@/pages/survey' import store from '@/store/index'; @@ -45,9 +46,7 @@ const routes = [ component: submissions, meta: {filter: true} }, - ] - }, {path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}}, {path: '/new-room/', name: 'new-room', component: newRoom}, @@ -87,6 +86,10 @@ const routes = [ {path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}}, ] }, + { + path: '/survey', + component: surveyPage + }, {path: '*', component: p404} ]; From c9caaf79db274f81c2cb7e1dec87a8a2de31907c Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 20 Jun 2019 14:51:43 +0200 Subject: [PATCH 2/7] Add initial implementation of survey model --- server/core/settings.py | 1 + .../migrations/0005_auto_20190617_1115.py | 20 ++++++++++++++++ server/surveys/__init__.py | 0 server/surveys/admin.py | 3 +++ server/surveys/apps.py | 5 ++++ server/surveys/migrations/0001_initial.py | 23 +++++++++++++++++++ server/surveys/migrations/__init__.py | 0 server/surveys/models.py | 10 ++++++++ server/surveys/tests.py | 3 +++ server/surveys/views.py | 3 +++ 10 files changed, 68 insertions(+) create mode 100644 server/rooms/migrations/0005_auto_20190617_1115.py create mode 100644 server/surveys/__init__.py create mode 100644 server/surveys/admin.py create mode 100644 server/surveys/apps.py create mode 100644 server/surveys/migrations/0001_initial.py create mode 100644 server/surveys/migrations/__init__.py create mode 100644 server/surveys/models.py create mode 100644 server/surveys/tests.py create mode 100644 server/surveys/views.py diff --git a/server/core/settings.py b/server/core/settings.py index 12964ab8..b0720a86 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -53,6 +53,7 @@ INSTALLED_APPS = [ 'basicknowledge', 'portfolio', 'statistics', + 'surveys', 'wagtail.contrib.forms', 'wagtail.contrib.redirects', diff --git a/server/rooms/migrations/0005_auto_20190617_1115.py b/server/rooms/migrations/0005_auto_20190617_1115.py new file mode 100644 index 00000000..d5d48c75 --- /dev/null +++ b/server/rooms/migrations/0005_auto_20190617_1115.py @@ -0,0 +1,20 @@ +# Generated by Django 2.0.6 on 2019-06-17 11:15 + +from django.db import migrations +import wagtail.core.blocks +import wagtail.core.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('rooms', '0004_auto_20190210_2125'), + ] + + operations = [ + migrations.AlterField( + model_name='roomentry', + name='contents', + field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True), + ), + ] diff --git a/server/surveys/__init__.py b/server/surveys/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/surveys/admin.py b/server/surveys/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/server/surveys/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/surveys/apps.py b/server/surveys/apps.py new file mode 100644 index 00000000..861dd6e2 --- /dev/null +++ b/server/surveys/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SurveysConfig(AppConfig): + name = 'surveys' diff --git a/server/surveys/migrations/0001_initial.py b/server/surveys/migrations/0001_initial.py new file mode 100644 index 00000000..35a3e25f --- /dev/null +++ b/server/surveys/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.6 on 2019-06-17 11:15 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Survey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('data', django.contrib.postgres.fields.jsonb.JSONField()), + ], + ), + ] diff --git a/server/surveys/migrations/__init__.py b/server/surveys/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/surveys/models.py b/server/surveys/models.py new file mode 100644 index 00000000..423da6fd --- /dev/null +++ b/server/surveys/models.py @@ -0,0 +1,10 @@ +from django.db import models +from django.contrib.postgres.fields import JSONField + + +class Survey(models.Model): + title = models.CharField(max_length=255) + data = JSONField() + + def __str__(self): + return self.title diff --git a/server/surveys/tests.py b/server/surveys/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/server/surveys/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/surveys/views.py b/server/surveys/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/server/surveys/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From f9642ff49ed7cfb0532b983f6f7f0b1827189959 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 27 Jun 2019 17:55:07 +0200 Subject: [PATCH 3/7] Get survey from server and display it dynamically --- client/src/graphql/gql/surveyQuery.gql | 10 +++ client/src/main.js | 1 - client/src/pages/survey.vue | 102 +++++++++++++++++++++---- client/src/router/index.js | 7 +- client/src/styles/_survey.scss | 28 +++++++ client/src/styles/main.scss | 1 + 6 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 client/src/graphql/gql/surveyQuery.gql create mode 100644 client/src/styles/_survey.scss diff --git a/client/src/graphql/gql/surveyQuery.gql b/client/src/graphql/gql/surveyQuery.gql new file mode 100644 index 00000000..19f60bd7 --- /dev/null +++ b/client/src/graphql/gql/surveyQuery.gql @@ -0,0 +1,10 @@ +query SurveyQuery($id: ID!) { + survey(id: $id) { + id + title + data + answer { + data + } + } +} diff --git a/client/src/main.js b/client/src/main.js index d8157873..d34edd7c 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -108,7 +108,6 @@ Vue.use(VeeValidate, { Vue.filter('date', dateFilter); - /* eslint-disable no-new */ new Vue({ el: '#app', diff --git a/client/src/pages/survey.vue b/client/src/pages/survey.vue index 2ab45cdd..fc8e0e62 100644 --- a/client/src/pages/survey.vue +++ b/client/src/pages/survey.vue @@ -1,37 +1,107 @@ + + diff --git a/client/src/router/index.js b/client/src/router/index.js index 6b9b54b3..a06fc241 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -25,6 +25,7 @@ import Router from 'vue-router' import editProject from '@/pages/editProject' import newProject from '@/pages/newProject' import surveyPage from '@/pages/survey' +// import styleGuidePage from '@/pages/styleguide' import store from '@/store/index'; @@ -87,9 +88,11 @@ const routes = [ ] }, { - path: '/survey', - component: surveyPage + path: '/survey/:id', + component: surveyPage, + props: true }, + // {path: '/styleguide', component: styleGuidePage}, {path: '*', component: p404} ]; diff --git a/client/src/styles/_survey.scss b/client/src/styles/_survey.scss new file mode 100644 index 00000000..c854eb23 --- /dev/null +++ b/client/src/styles/_survey.scss @@ -0,0 +1,28 @@ +.survey { + &__panel-title { + @include main-title; + margin-bottom: $large-spacing*2; + span { + @include main-title; + } + } + + &__panel-description { + @include regular-paragraph; + line-height: 1.5; + margin-bottom: $large-spacing; + } + + &__question-title { + @include heading-4; + margin-bottom: $medium-spacing; + span { + @include heading-4; + } + } + + &__input { + width: 100%; + margin-bottom: $medium-spacing; + } +} diff --git a/client/src/styles/main.scss b/client/src/styles/main.scss index 87f9760f..7315029b 100644 --- a/client/src/styles/main.scss +++ b/client/src/styles/main.scss @@ -14,3 +14,4 @@ @import "article"; @import "actions"; @import "top-navigation"; +@import "survey"; From 9b855607958b8ff448e978a550639deac3b338e4 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 27 Jun 2019 17:56:29 +0200 Subject: [PATCH 4/7] Add answers to surveys --- .../graphql/gql/mutations/updateAnswer.gql | 20 +++ client/src/survey.config.js | 161 ++++++++++++++++++ server/api/schema.py | 6 +- server/api/utils.py | 6 + server/surveys/admin.py | 37 ++++ server/surveys/inputs.py | 6 + server/surveys/migrations/0002_answer.py | 26 +++ server/surveys/models.py | 9 + server/surveys/mutations.py | 37 ++++ server/surveys/schema.py | 49 ++++++ 10 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 client/src/graphql/gql/mutations/updateAnswer.gql create mode 100644 client/src/survey.config.js create mode 100644 server/surveys/inputs.py create mode 100644 server/surveys/migrations/0002_answer.py create mode 100644 server/surveys/mutations.py create mode 100644 server/surveys/schema.py diff --git a/client/src/graphql/gql/mutations/updateAnswer.gql b/client/src/graphql/gql/mutations/updateAnswer.gql new file mode 100644 index 00000000..a080a191 --- /dev/null +++ b/client/src/graphql/gql/mutations/updateAnswer.gql @@ -0,0 +1,20 @@ +mutation UpdateAnswer($input:UpdateAnswerInput!) { + updateAnswer(input:$input){ + answer { + id + data + } + } +} + + +# input + +#{ +# "input": { +# "answer": { +# "surveyId": "U3VydmV5Tm9kZTox", +# "data": "{\"some\": \"json\"}" +# }, +# } +#} diff --git a/client/src/survey.config.js b/client/src/survey.config.js new file mode 100644 index 00000000..a6b2213b --- /dev/null +++ b/client/src/survey.config.js @@ -0,0 +1,161 @@ +export const css = { + 'root': 'survey', + 'header': '', + 'body': '', + 'bodyEmpty': '', + 'footer': '', + 'navigationButton': 'button button--primary', + 'completedPage': '', + 'navigation': { + 'complete': 'button button--primary', + 'prev': 'button button--primary', + 'next': 'button button--primary', + 'start': 'button button--primary' + }, + 'progress': 'progress center-block mx-auto mb-4', + 'progressBar': 'progress-bar', + 'page': { + 'root': '', + 'title': '', + 'survey__page-description': '' + }, + 'pageTitle': '', + 'pageDescription': 'small', + 'row': 'sv_row', + 'question': { + 'mainRoot': 'survey__question question', + 'flowRoot': 'sv_q_flow sv_qstn', + 'titleLeftRoot': 'sv_qstn_left', + 'title': 'survey__question-title', + 'number': 'sv_q_num', + 'survey__question-description': 'small', + 'comment': 'survey__question-input skillbox-input', + 'required': '', + 'titleRequired': '', + 'hasError': 'has-error', + 'indent': 20 + }, + 'panel': { + 'title': 'survey__panel-title', + 'description': 'small survey__panel-description', + 'container': 'sv_p_container' + }, + 'error': { + 'root': 'alert alert-danger', + 'icon': 'glyphicon glyphicon-exclamation-sign', + 'item': '', + 'locationTop': 'sv_qstn_error_top', + 'locationBottom': 'sv_qstn_error_bottom' + }, + 'boolean': { + 'root': 'sv_qbln form-inline checkbox', + 'item': '', + 'label': '', + 'materialDecorator': 'checkbox-material' + }, + 'checkbox': { + 'root': 'sv_qcbc sv_qcbx form-inline', + 'item': 'checkbox', + 'itemControl': '', + 'controlLabel': '', + 'materialDecorator': 'checkbox-material', + 'other': 'sv_q_checkbox_other skillbox-input', + 'column': 'sv_q_select_column' + }, + 'comment': 'survey__input skillbox-input', + 'dropdown': { + 'root': '', + 'control': 'skillbox-input', + 'other': 'sv_q_dd_other skillbox-input' + }, + 'html': { + 'root': '' + }, + 'matrix': { + 'root': 'table table-striped', + 'label': 'sv_q_m_label', + 'cellText': 'sv_q_m_cell_text', + 'cellTextSelected': 'sv_q_m_cell_selected bg-primary', + 'cellLabel': 'sv_q_m_cell_label' + }, + 'matrixdropdown': { + 'root': 'table' + }, + 'matrixdynamic': { + 'root': 'table', + 'button': 'button', + 'buttonAdd': '', + 'buttonRemove': '', + 'iconAdd': '', + 'iconRemove': '' + }, + 'paneldynamic': { + 'root': '', + 'button': 'button', + 'buttonPrev': '', + 'buttonNext': '', + 'buttonAdd': '', + 'buttonRemove': '' + }, + 'multipletext': { + 'root': 'table', + 'itemTitle': '', + 'itemValue': 'sv_q_mt_item_value skillbox-input' + }, + 'radiogroup': { + 'root': 'sv_qcbc form-inline', + 'item': 'radio', + 'label': '', + 'itemControl': '', + 'controlLabel': '', + 'materialDecorator': 'circle', + 'other': 'sv_q_radiogroup_other skillbox-input', + 'clearButton': 'sv_q_radiogroup_clear button', + 'column': 'sv_q_select_column' + }, + 'imagepicker': { + 'root': 'sv_imgsel', + 'item': 'sv_q_imgsel', + 'label': 'sv_q_imgsel_label', + 'itemControl': 'sv_q_imgsel_control_item', + 'image': 'sv_q_imgsel_image', + 'itemText': 'sv_q_imgsel_text', + 'clearButton': 'sv_q_radiogroup_clear' + }, + 'rating': { + 'root': 'btn-group', + 'item': 'btn btn-default btn-secondary', + 'selected': 'active', + 'minText': 'sv_q_rating_min_text', + 'itemText': 'sv_q_rating_item_text', + 'maxText': 'sv_q_rating_max_text' + }, + 'text': 'survey__input skillbox-input', + 'expression': 'survey__input skillbox-input', + 'file': { + 'root': 'sv_q_file', + 'placeholderInput': 'sv_q_file_placeholder', + 'preview': 'sv_q_file_preview', + 'removeButton': 'sv_q_file_remove_button', + 'fileInput': 'sv_q_file_input', + 'removeFile': 'sv_q_file_remove' + }, + 'saveData': { + 'root': '', + 'saving': 'alert alert-info', + 'error': 'alert alert-danger', + 'success': 'alert alert-success', + 'saveAgainButton': '' + }, + 'window': { + 'root': 'modal-content', + 'body': 'modal-body', + 'header': { + 'root': 'modal-header panel-title', + 'title': 'pull-left', + 'button': 'glyphicon pull-right', + 'buttonExpanded': 'glyphicon pull-right glyphicon-chevron-up', + 'buttonCollapsed': 'glyphicon pull-right glyphicon-chevron-down' + } + } +}; diff --git a/server/api/schema.py b/server/api/schema.py index 961cdf35..9c39b4db 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -15,6 +15,8 @@ from objectives.mutations import ObjectiveMutations from objectives.schema import ObjectivesQuery from portfolio.mutations import PortfolioMutations from portfolio.schema import PortfolioQuery +from surveys.schema import SurveysQuery +from surveys.mutations import SurveysMutations from rooms.mutations import RoomMutations from rooms.schema import RoomsQuery from users.schema import UsersQuery @@ -22,7 +24,7 @@ from users.mutations import ProfileMutations class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery, - BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, graphene.ObjectType): + BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType): node = relay.Node.Field() if settings.DEBUG: @@ -30,7 +32,7 @@ class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations, - ProfileMutations, graphene.ObjectType): + ProfileMutations, SurveysMutations, graphene.ObjectType): if settings.DEBUG: debug = graphene.Field(DjangoDebug, name='__debug') diff --git a/server/api/utils.py b/server/api/utils.py index 6cd70ac4..a77a2dcd 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -56,6 +56,12 @@ def get_graphql_mutation(filename): return mutation +def get_by_id(model, **kwargs): + id = kwargs.get('id') + if id is not None: + return get_object(model, id) + return None + def get_by_id_or_slug(model, **kwargs): slug = kwargs.get('slug') id = kwargs.get('id') diff --git a/server/surveys/admin.py b/server/surveys/admin.py index 8c38f3f3..b7873b3a 100644 --- a/server/surveys/admin.py +++ b/server/surveys/admin.py @@ -1,3 +1,40 @@ +import json +import logging + from django.contrib import admin +from django.contrib.postgres.fields import JSONField +from django.forms import widgets + +logger = logging.getLogger(__name__) # Register your models here. +from surveys.models import Survey, Answer + + +class PrettyJSONWidget(widgets.Textarea): + + def format_value(self, value): + try: + value = json.dumps(json.loads(value), indent=2, sort_keys=True) + # these lines will try to adjust size of TextArea to fit to content + row_lengths = [len(r) for r in value.split('\n')] + self.attrs['rows'] = min(max(len(row_lengths) + 2, 10), 30) + self.attrs['cols'] = min(max(max(row_lengths) + 2, 40), 120) + return value + except Exception as e: + logger.warning("Error while formatting JSON: {}".format(e)) + return super(PrettyJSONWidget, self).format_value(value) + + +class JSONAdmin(admin.ModelAdmin): + formfield_overrides = { + JSONField: {'widget': PrettyJSONWidget} + } + +@admin.register(Survey) +class SurveyAdmin(JSONAdmin): + pass + +@admin.register(Answer) +class AnswerAdmin(JSONAdmin): + pass diff --git a/server/surveys/inputs.py b/server/surveys/inputs.py new file mode 100644 index 00000000..11a45b9c --- /dev/null +++ b/server/surveys/inputs.py @@ -0,0 +1,6 @@ +import graphene +from graphene import InputObjectType + +class UpdateAnswerArgument(InputObjectType): + survey_id = graphene.ID(required=True) + data = graphene.String(required=True) diff --git a/server/surveys/migrations/0002_answer.py b/server/surveys/migrations/0002_answer.py new file mode 100644 index 00000000..21bdf24b --- /dev/null +++ b/server/surveys/migrations/0002_answer.py @@ -0,0 +1,26 @@ +# Generated by Django 2.0.6 on 2019-06-27 14:35 + +from django.conf import settings +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('surveys', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Answer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('data', django.contrib.postgres.fields.jsonb.JSONField()), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.Survey')), + ], + ), + ] diff --git a/server/surveys/models.py b/server/surveys/models.py index 423da6fd..1f137f71 100644 --- a/server/surveys/models.py +++ b/server/surveys/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from django.db import models from django.contrib.postgres.fields import JSONField @@ -8,3 +9,11 @@ class Survey(models.Model): def __str__(self): return self.title + +class Answer(models.Model): + owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='answers') + data = JSONField() + survey = models.ForeignKey(Survey, on_delete=models.CASCADE, related_name='answers') + + def __str__(self): + return '{} - {}'.format(self.owner.username, self.survey.title) diff --git a/server/surveys/mutations.py b/server/surveys/mutations.py new file mode 100644 index 00000000..0fb962e8 --- /dev/null +++ b/server/surveys/mutations.py @@ -0,0 +1,37 @@ +import graphene +import json +from graphene import relay + +from api.utils import get_object +from surveys.inputs import UpdateAnswerArgument +from surveys.models import Survey, Answer +from surveys.schema import AnswerNode + + +class UpdateAnswer(relay.ClientIDMutation): + class Input: + answer = graphene.Argument(UpdateAnswerArgument) + + answer = graphene.Field(AnswerNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + user = info.context.user + + answer = kwargs.get('answer') + survey_id = answer.get('survey_id') + data = json.loads(answer.get('data')) + survey = get_object(Survey, survey_id) + + try: + answer = survey.answers.get(owner=user) + answer.data = data + answer.save() + except Answer.DoesNotExist: + answer = Answer.objects.create(owner=user, survey=survey, data=data) + + return cls(answer=answer) + + +class SurveysMutations: + update_answer = UpdateAnswer.Field() diff --git a/server/surveys/schema.py b/server/surveys/schema.py new file mode 100644 index 00000000..701e76cd --- /dev/null +++ b/server/surveys/schema.py @@ -0,0 +1,49 @@ +import graphene +from graphene import relay +from graphene_django import DjangoObjectType +from graphene_django.filter import DjangoFilterConnectionField + +from api.utils import get_by_id +from surveys.models import Answer +from .models import Survey + + +class AnswerNode(DjangoObjectType): + pk = graphene.Int() + + class Meta: + model = Answer + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + +class SurveyNode(DjangoObjectType): + pk = graphene.Int() + answer = graphene.Field(AnswerNode) + + class Meta: + model = Survey + filter_fields = [] + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + def resolve_answer(self, info, **kwargs): + user = info.context.user + try: + return Answer.objects.get(owner=user, survey=self) + except Answer.DoesNotExist: + return None + +class SurveysQuery(object): + survey = graphene.Field(SurveyNode, id=graphene.ID()) + surveys = DjangoFilterConnectionField(SurveyNode) + + def resolve_surveys(self, info, **kwargs): + return Survey.objects.all() + + def resolve_survey(self, info, **kwargs): + return get_by_id(Survey, **kwargs) From fbe39e278462bc82507572642fe23e60bc48d5fa Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 27 Jun 2019 17:56:49 +0200 Subject: [PATCH 5/7] Add style guide --- client/src/pages/styleguide.vue | 51 +++++++++++++++++++++++++++++++++ client/src/router/index.js | 4 +-- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 client/src/pages/styleguide.vue diff --git a/client/src/pages/styleguide.vue b/client/src/pages/styleguide.vue new file mode 100644 index 00000000..cdef142f --- /dev/null +++ b/client/src/pages/styleguide.vue @@ -0,0 +1,51 @@ + + + diff --git a/client/src/router/index.js b/client/src/router/index.js index a06fc241..d3d3d629 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -25,7 +25,7 @@ import Router from 'vue-router' import editProject from '@/pages/editProject' import newProject from '@/pages/newProject' import surveyPage from '@/pages/survey' -// import styleGuidePage from '@/pages/styleguide' +import styleGuidePage from '@/pages/styleguide' import store from '@/store/index'; @@ -92,7 +92,7 @@ const routes = [ component: surveyPage, props: true }, - // {path: '/styleguide', component: styleGuidePage}, + {path: '/styleguide', component: styleGuidePage}, {path: '*', component: p404} ]; From b498dc4efa75ab00bef1e602544bc4953129cbc1 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 1 Jul 2019 13:08:42 +0200 Subject: [PATCH 6/7] Add cypress test for surveys --- client/cypress/integration/survey.spec.js | 34 +++++++++ client/package-lock.json | 28 +++++-- client/src/pages/survey.vue | 2 +- .../commands/prepare_surveys_for_cypress.py | 75 +++++++++++++++++++ 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 client/cypress/integration/survey.spec.js create mode 100644 server/core/management/commands/prepare_surveys_for_cypress.py diff --git a/client/cypress/integration/survey.spec.js b/client/cypress/integration/survey.spec.js new file mode 100644 index 00000000..08d3ec96 --- /dev/null +++ b/client/cypress/integration/survey.spec.js @@ -0,0 +1,34 @@ +describe('Survey', () => { + beforeEach(() => { + cy.exec("python ../server/manage.py prepare_surveys_for_cypress"); + + cy.viewport('macbook-15'); + cy.startGraphQLCapture(); + cy.login('rahel.cueni', 'test'); + }); + + it('should display and fill out the survey', () => { + cy.visit('/survey/U3VydmV5Tm9kZTox'); + + cy.get('.survey__panel-title').should('contain', 'Fall 1') + + cy.get('#sq_100i').type('Wohlwollen'); + cy.get('#sq_101i').type('Demut'); + + cy.get('[value=Next]').click(); + //cy.get('.button--primary').click() + + cy.get('#sq_102i').type('Keuschheit'); + cy.get('#sq_103i').type('Geduld'); + + cy.get('[value=Complete]').click(); + + cy.waitFor('UpdateAnswer'); + + cy.visit('/survey/U3VydmV5Tm9kZTox'); + + cy.get('#sq_100i').should('have.value', 'Wohlwollen') + + }); + +}); diff --git a/client/package-lock.json b/client/package-lock.json index 9c4872c8..817de619 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4674,11 +4674,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4691,15 +4693,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4802,7 +4807,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4812,6 +4818,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4824,17 +4831,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4851,6 +4861,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4923,7 +4934,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4933,6 +4945,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5038,6 +5051,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/client/src/pages/survey.vue b/client/src/pages/survey.vue index fc8e0e62..75f6de93 100644 --- a/client/src/pages/survey.vue +++ b/client/src/pages/survey.vue @@ -73,7 +73,7 @@ console.log(data); let json = JSON.parse(data.survey.data); let answer = {}; - if (data.survey.answer.data && data.survey.answer) { + if (data.survey.answer && data.survey.answer.data) { answer = JSON.parse(data.survey.answer.data); } diff --git a/server/core/management/commands/prepare_surveys_for_cypress.py b/server/core/management/commands/prepare_surveys_for_cypress.py new file mode 100644 index 00000000..1d0b21a2 --- /dev/null +++ b/server/core/management/commands/prepare_surveys_for_cypress.py @@ -0,0 +1,75 @@ +from django.core.management import BaseCommand + +from portfolio.factories import ProjectFactory +from portfolio.models import ProjectEntry +from surveys.models import Survey +from users.models import User + +survey_data = { + "pages": [ + { + "elements": [ + { + "description": "Max hat Ende Monat noch Fr. 20.\u2013 \u00fcbrig, die er gespart hat, um mit seinem besten Kumpel, der ein halbes Jahr im Ausland verweilte, Billard spielen zu gehen. Doch dann bittet ihn seine j\u00fcngere Schwester um Geld. Sie hat ein unverhofftes Date mit einem jungen Mann, in den sie sich bereits vor Monaten unsterblich verliebt hat. Leider ist ihr Kontostand aber bereits auf Null.", + "elements": [ + { + "name": "A: Max gibt ihr das Geld und muss das Billardspiel absagen.", + "placeHolder": "Passende Tugenden erfassen...", + "type": "text" + }, + { + "name": "question2", + "placeHolder": "Passende Tugenden erfassen...", + "title": "B: Max gibt ihr das Geld nicht und geht Billard spielen.", + "type": "text" + } + ], + "name": "Fall 1", + "title": "Fall 1", + "type": "panel" + } + ], + "name": "Seite 1" + }, + { + "elements": [ + { + "description": "Auf der Autobahn brennt ein Lastwagen, der jederzeit explodieren kann. Silvio, dem Fahrer, bleiben nur noch wenige Minuten: Entweder bringt er seinen ohnm\u00e4chtig gewordenen Mitfahrer in Sicherheit oder er sperrt die Strasse ab, die nach wie vor dicht befahren wird.", + "elements": [ + { + "name": "question1", + "placeHolder": "Passende Tugenden erfassen...", + "title": "A: Silvio bringt seinen Mitfahrer in Sicherheit.", + "type": "text", + "useDisplayValuesInTitle": False + }, + { + "name": "question3", + "placeHolder": "Passende Tugenden erfassen...", + "title": "B: Silvio sperrt die Strasse ab.", + "type": "text" + } + ], + "name": "panel1", + "title": "Fall 2", + "type": "panel" + } + ], + "name": "Seite 2" + } + ], + "showQuestionNumbers": "off" +} + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.stdout.write("Clearing surveys") + Survey.objects.all().delete() + self.stdout.write("Creating survey") + + Survey.objects.create( + title='Test', + data=survey_data, + pk=1 + ) From e296d500aa1d24a64814469c081232b660487c1b Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 4 Jul 2019 10:24:12 +0200 Subject: [PATCH 7/7] Clean up code --- client/src/pages/survey.vue | 4 ---- server/api/utils.py | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/client/src/pages/survey.vue b/client/src/pages/survey.vue index 75f6de93..bd83dbc5 100644 --- a/client/src/pages/survey.vue +++ b/client/src/pages/survey.vue @@ -23,7 +23,6 @@ data() { return { - // survey survey: this.initSurvey(), title: '' } @@ -35,7 +34,6 @@ survey.data = answer; survey.onComplete.add((sender, options) => { - console.log(survey.data); sender.clear(false); sender.mode = 'display'; @@ -70,14 +68,12 @@ manual: true, result({data, loading, networkStatus}) { if (!loading) { - console.log(data); let json = JSON.parse(data.survey.data); let answer = {}; if (data.survey.answer && data.survey.answer.data) { answer = JSON.parse(data.survey.answer.data); } - console.log(json); this.survey = this.initSurvey(json, answer); this.title = data.survey.title; } diff --git a/server/api/utils.py b/server/api/utils.py index a77a2dcd..3b3e76f1 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -58,9 +58,9 @@ def get_graphql_mutation(filename): def get_by_id(model, **kwargs): id = kwargs.get('id') - if id is not None: - return get_object(model, id) - return None + + return get_object(model, id) if id is not None else None + def get_by_id_or_slug(model, **kwargs): slug = kwargs.get('slug')