Merged in feature/teams (pull request #81)
Feature/teams Approved-by: Christian Cueni
This commit is contained in:
commit
da2253a73d
|
|
@ -15,6 +15,7 @@
|
||||||
},
|
},
|
||||||
"onboardingVisited": false,
|
"onboardingVisited": false,
|
||||||
"__typename": "UserNode",
|
"__typename": "UserNode",
|
||||||
"permissions": []
|
"permissions": [],
|
||||||
|
"team": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@ const schema = require('../../fixtures/schema.json');
|
||||||
const me = require('../../fixtures/me.new-student.json');
|
const me = require('../../fixtures/me.new-student.json');
|
||||||
|
|
||||||
describe('New student', () => {
|
describe('New student', () => {
|
||||||
it('shows "Enter Code" page and adds the user to a class', () => {
|
before(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
cy.task('getSchema').then(schema => {
|
||||||
cy.mockGraphql({
|
cy.mockGraphql({
|
||||||
schema: schema,
|
schema,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "Enter Code" page and adds the user to a class', () => {
|
||||||
cy.apolloLogin('hansli', 'test');
|
cy.apolloLogin('hansli', 'test');
|
||||||
|
|
||||||
const __typename = 'SchoolClassNode';
|
const __typename = 'SchoolClassNode';
|
||||||
|
|
@ -26,9 +29,9 @@ describe('New student', () => {
|
||||||
schoolClass: {
|
schoolClass: {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
__typename
|
__typename,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
MySchoolClassQuery: {
|
MySchoolClassQuery: {
|
||||||
me: {
|
me: {
|
||||||
|
|
@ -37,21 +40,21 @@ describe('New student', () => {
|
||||||
__typename,
|
__typename,
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
members: []
|
members: [],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
...mockUpdateOnboardingProgress()
|
...mockUpdateOnboardingProgress(),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.get('[data-cy=join-class-title]').should('contain', 'Einer Klasse beitreten');
|
cy.get('[data-cy=join-form-title]').should('contain', 'Einer Klasse beitreten');
|
||||||
cy.get('[data-cy=input-class-code]').type('XXXX');
|
cy.get('[data-cy=input-form-code]').type('XXXX');
|
||||||
cy.get('[data-cy=join-class]').click();
|
cy.get('[data-cy=join-form-confirm]').click();
|
||||||
cy.skipOnboarding();
|
cy.skipOnboarding();
|
||||||
cy.get('[data-cy=user-widget-avatar]').click();
|
cy.get('[data-cy=user-widget-avatar]').click();
|
||||||
cy.get('[data-cy=class-list-link]').click();
|
cy.get('[data-cy=class-list-link]').click();
|
||||||
cy.get('[data-cy=class-list-title]').should('contain', 'Klassenliste');
|
cy.get('[data-cy=group-list-title]').should('contain', 'Klassenliste');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import {mockUpdateOnboardingProgress} from '../../support/helpers';
|
import {mockUpdateOnboardingProgress} from '../../support/helpers';
|
||||||
|
|
||||||
const schema = require('../../fixtures/schema.json');
|
|
||||||
const me = require('../../fixtures/me.join-class.json');
|
const me = require('../../fixtures/me.join-class.json');
|
||||||
|
|
||||||
describe('Onboarding', () => {
|
describe('Onboarding', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
cy.mockGraphql({
|
cy.task('getSchema').then(schema => {
|
||||||
schema: schema,
|
cy.mockGraphql({
|
||||||
|
schema,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,96 @@
|
||||||
const schema = require('../../fixtures/schema.json');
|
|
||||||
const me = require('../../fixtures/me.new-student.json');
|
const operations = {
|
||||||
|
MeQuery: {
|
||||||
|
me: {
|
||||||
|
id: 'VXNlck5vZGU6NQ==',
|
||||||
|
onboardingVisited: true,
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProjectsQuery: {
|
||||||
|
projects: {
|
||||||
|
edges: [{
|
||||||
|
node: {
|
||||||
|
id: 'UHJvamVjdE5vZGU6MzM=',
|
||||||
|
title: 'Groot',
|
||||||
|
appearance: 'red',
|
||||||
|
'description': 'I am Groot',
|
||||||
|
'slug': 'groot',
|
||||||
|
'objectives': 'Be Groot\nBe awesome',
|
||||||
|
'final': false,
|
||||||
|
'student': {
|
||||||
|
'firstName': 'Rahel',
|
||||||
|
'lastName': 'Cueni',
|
||||||
|
'id': 'VXNlck5vZGU6NQ==',
|
||||||
|
'avatarUrl': '',
|
||||||
|
'__typename': 'UserNode',
|
||||||
|
},
|
||||||
|
'entriesCount': 2,
|
||||||
|
'__typename': 'ProjectNode',
|
||||||
|
},
|
||||||
|
'__typename': 'ProjectNodeEdge',
|
||||||
|
}],
|
||||||
|
'__typename': 'ProjectNodeConnection',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProjectQuery: {
|
||||||
|
'project': {
|
||||||
|
'id': 'UHJvamVjdE5vZGU6MzY=',
|
||||||
|
'title': 'Groot',
|
||||||
|
'appearance': 'yellow',
|
||||||
|
'description': 'I am Groot',
|
||||||
|
'slug': 'groot',
|
||||||
|
'objectives': 'Be Groot\nBe awesome',
|
||||||
|
'final': false,
|
||||||
|
'student': {
|
||||||
|
'firstName': 'Rahel',
|
||||||
|
'lastName': 'Cueni',
|
||||||
|
'id': 'VXNlck5vZGU6NQ==',
|
||||||
|
'avatarUrl': '',
|
||||||
|
'__typename': 'UserNode',
|
||||||
|
},
|
||||||
|
'entriesCount': 1,
|
||||||
|
'__typename': 'ProjectNode',
|
||||||
|
'entries': {
|
||||||
|
'edges': [{
|
||||||
|
'node': {
|
||||||
|
'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
|
||||||
|
'activity': 'Kill Thanos',
|
||||||
|
'reflection': 'He sucks',
|
||||||
|
'nextSteps': 'Go for the head',
|
||||||
|
'documentUrl': '',
|
||||||
|
'__typename': 'ProjectEntryNode',
|
||||||
|
'created': '2020-01-20T15:20:31.262510+00:00',
|
||||||
|
},
|
||||||
|
'__typename': 'ProjectEntryNodeEdge',
|
||||||
|
}],
|
||||||
|
'__typename': 'ProjectEntryNodeConnection',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AddProjectEntry: variables => ({
|
||||||
|
addProjectEntry: {
|
||||||
|
projectEntry: Object.assign({}, variables.input.projectEntry, {
|
||||||
|
created: '2020-01-20T15:26:58.722773+00:00',
|
||||||
|
}),
|
||||||
|
errors: null,
|
||||||
|
__typename: 'AddProjectEntryPayload',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
UpdateProjectEntry: variables => ({
|
||||||
|
updateProjectEntry: {
|
||||||
|
projectEntry: variables.input.projectEntry,
|
||||||
|
errors: null,
|
||||||
|
__typename: 'UpdateProjectEntryPayload',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
DeleteProjectEntry: {
|
||||||
|
deleteProjectEntry: {
|
||||||
|
success: true,
|
||||||
|
__typename: 'DeleteProjectEntryPayload',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('Project Entry', () => {
|
describe('Project Entry', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -7,100 +98,11 @@ describe('Project Entry', () => {
|
||||||
cy.fakeLogin('rahel.cueni', 'test');
|
cy.fakeLogin('rahel.cueni', 'test');
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
cy.mockGraphql({
|
cy.task('getSchema').then(schema => {
|
||||||
schema: schema,
|
cy.mockGraphql({
|
||||||
operations: {
|
schema,
|
||||||
MeQuery: {
|
operations,
|
||||||
me: {
|
});
|
||||||
id: 'VXNlck5vZGU6NQ==',
|
|
||||||
onboardingVisited: true,
|
|
||||||
permissions: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ProjectsQuery: {
|
|
||||||
projects: {
|
|
||||||
edges: [{
|
|
||||||
node: {
|
|
||||||
id: 'UHJvamVjdE5vZGU6MzM=',
|
|
||||||
title: 'Groot',
|
|
||||||
appearance: 'red',
|
|
||||||
'description': 'I am Groot',
|
|
||||||
'slug': 'groot',
|
|
||||||
'objectives': 'Be Groot\nBe awesome',
|
|
||||||
'final': false,
|
|
||||||
'student': {
|
|
||||||
'firstName': 'Rahel',
|
|
||||||
'lastName': 'Cueni',
|
|
||||||
'id': 'VXNlck5vZGU6NQ==',
|
|
||||||
'avatarUrl': '',
|
|
||||||
'__typename': 'UserNode'
|
|
||||||
},
|
|
||||||
'entriesCount': 2,
|
|
||||||
'__typename': 'ProjectNode'
|
|
||||||
},
|
|
||||||
'__typename': 'ProjectNodeEdge'
|
|
||||||
}],
|
|
||||||
'__typename': 'ProjectNodeConnection'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ProjectQuery: {
|
|
||||||
'project': {
|
|
||||||
'id': 'UHJvamVjdE5vZGU6MzY=',
|
|
||||||
'title': 'Groot',
|
|
||||||
'appearance': 'yellow',
|
|
||||||
'description': 'I am Groot',
|
|
||||||
'slug': 'groot',
|
|
||||||
'objectives': 'Be Groot\nBe awesome',
|
|
||||||
'final': false,
|
|
||||||
'student': {
|
|
||||||
'firstName': 'Rahel',
|
|
||||||
'lastName': 'Cueni',
|
|
||||||
'id': 'VXNlck5vZGU6NQ==',
|
|
||||||
'avatarUrl': '',
|
|
||||||
'__typename': 'UserNode'
|
|
||||||
},
|
|
||||||
'entriesCount': 1,
|
|
||||||
'__typename': 'ProjectNode',
|
|
||||||
'entries': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
|
|
||||||
'activity': 'Kill Thanos',
|
|
||||||
'reflection': 'He sucks',
|
|
||||||
'nextSteps': 'Go for the head',
|
|
||||||
'documentUrl': '',
|
|
||||||
'__typename': 'ProjectEntryNode',
|
|
||||||
'created': '2020-01-20T15:20:31.262510+00:00'
|
|
||||||
},
|
|
||||||
'__typename': 'ProjectEntryNodeEdge'
|
|
||||||
}],
|
|
||||||
'__typename': 'ProjectEntryNodeConnection'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AddProjectEntry: variables => ({
|
|
||||||
addProjectEntry: {
|
|
||||||
projectEntry: Object.assign({}, variables.input.projectEntry, {
|
|
||||||
created: '2020-01-20T15:26:58.722773+00:00'
|
|
||||||
}),
|
|
||||||
errors: null,
|
|
||||||
__typename: 'AddProjectEntryPayload'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
UpdateProjectEntry: variables => ({
|
|
||||||
updateProjectEntry: {
|
|
||||||
projectEntry: variables.input.projectEntry,
|
|
||||||
errors: null,
|
|
||||||
__typename: 'UpdateProjectEntryPayload'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
DeleteProjectEntry: {
|
|
||||||
deleteProjectEntry: {
|
|
||||||
success: true,
|
|
||||||
__typename: 'DeleteProjectEntryPayload'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const schema = require('../../fixtures/schema.json');
|
|
||||||
const me = require('../../fixtures/me.join-class.json');
|
const me = require('../../fixtures/me.join-class.json');
|
||||||
const selectedClass = require('../../fixtures/selected-school-class.json');
|
const selectedClass = require('../../fixtures/selected-school-class.json');
|
||||||
|
|
||||||
|
|
@ -6,8 +5,10 @@ describe('Class Management', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
cy.mockGraphql({
|
cy.task('getSchema').then(schema => {
|
||||||
schema: schema,
|
cy.mockGraphql({
|
||||||
|
schema,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
@ -104,7 +105,7 @@ describe('Class Management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.visit('/me/my-class');
|
cy.visit('/me/my-class');
|
||||||
cy.get('[data-cy=school-class-member]').should('have.length', 2);
|
cy.get('[data-cy=group-list-member]').should('have.length', 2);
|
||||||
cy.get('[data-cy=remove-from-class]').should('have.length', 0);
|
cy.get('[data-cy=remove-from-class]').should('have.length', 0);
|
||||||
cy.get('[data-cy=add-to-class]').should('have.length', 0);
|
cy.get('[data-cy=add-to-class]').should('have.length', 0);
|
||||||
});
|
});
|
||||||
|
|
@ -187,8 +188,10 @@ describe('Teacher Class Management', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
cy.mockGraphql({
|
cy.task('getSchema').then(schema => {
|
||||||
schema: schema,
|
cy.mockGraphql({
|
||||||
|
schema,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
@ -229,10 +232,10 @@ describe('Teacher Class Management', () => {
|
||||||
|
|
||||||
cy.visit('/me/my-class');
|
cy.visit('/me/my-class');
|
||||||
|
|
||||||
cy.get('[data-cy=edit-class-name-link]').click();
|
cy.get('[data-cy=edit-group-name-link]').click();
|
||||||
cy.get('[data-cy=edit-class-name-input] input').type('{selectall}{backspace}').type(className);
|
cy.get('[data-cy=edit-name-input] input').type('{selectall}{backspace}').type(className);
|
||||||
cy.get('[data-cy=modal-save-button]').click();
|
cy.get('[data-cy=modal-save-button]').click();
|
||||||
cy.get('[data-cy=school-class-name]').should('contain', className);
|
cy.get('[data-cy=group-list-name]').should('contain', className);
|
||||||
});
|
});
|
||||||
|
|
||||||
// // fixme: cache misbehaves with mequery, but only for test
|
// // fixme: cache misbehaves with mequery, but only for test
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,37 @@
|
||||||
import {mockUpdateLastModule} from '../../support/helpers';
|
import {mockUpdateLastModule} from '../../support/helpers';
|
||||||
|
|
||||||
const schema = require('../../fixtures/schema.json');
|
|
||||||
const assignments = require('../../fixtures/assignments.json');
|
const assignments = require('../../fixtures/assignments.json');
|
||||||
const module = require('../../fixtures/module.json');
|
const module = require('../../fixtures/module.json');
|
||||||
const spellCheck = require('../../fixtures/spell-check.json');
|
const spellCheck = require('../../fixtures/spell-check.json');
|
||||||
|
|
||||||
|
const operations = {
|
||||||
|
MeQuery: {
|
||||||
|
me: {
|
||||||
|
permissions: [],
|
||||||
|
onboardingVisited: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AssignmentsQuery: {
|
||||||
|
assignments,
|
||||||
|
},
|
||||||
|
ModulesQuery: {
|
||||||
|
module,
|
||||||
|
},
|
||||||
|
SpellCheck: {
|
||||||
|
spellCheck,
|
||||||
|
},
|
||||||
|
...mockUpdateLastModule(),
|
||||||
|
};
|
||||||
|
|
||||||
describe('Spellcheck', () => {
|
describe('Spellcheck', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
cy.mockGraphql({
|
|
||||||
schema: schema,
|
cy.task('getSchema').then(schema => {
|
||||||
operations: {
|
cy.mockGraphql({
|
||||||
MeQuery: {
|
schema,
|
||||||
me: {
|
operations,
|
||||||
permissions: [],
|
});
|
||||||
onboardingVisited: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AssignmentsQuery: {
|
|
||||||
assignments
|
|
||||||
},
|
|
||||||
ModulesQuery: {
|
|
||||||
module
|
|
||||||
},
|
|
||||||
SpellCheck: {
|
|
||||||
spellCheck
|
|
||||||
},
|
|
||||||
...mockUpdateLastModule()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
const schema = require('../../fixtures/schema.json');
|
|
||||||
const module = require('../../fixtures/module.json');
|
const module = require('../../fixtures/module.json');
|
||||||
|
|
||||||
describe('Survey', () => {
|
describe('Survey', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
cy.mockGraphql({
|
cy.task('getSchema').then(schema => {
|
||||||
schema: schema,
|
cy.mockGraphql({
|
||||||
|
schema,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
:is="showModal"
|
:is="showModal"
|
||||||
v-if="showModal"/>
|
v-if="showModal"/>
|
||||||
<component :is="layout"/>
|
<component :is="layout"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -32,6 +31,7 @@
|
||||||
import NewNoteWizard from '@/components/notes/NewNoteWizard';
|
import NewNoteWizard from '@/components/notes/NewNoteWizard';
|
||||||
import EditNoteWizard from '@/components/notes/EditNoteWizard';
|
import EditNoteWizard from '@/components/notes/EditNoteWizard';
|
||||||
import EditClassNameWizard from '@/components/school-class/EditClassNameWizard';
|
import EditClassNameWizard from '@/components/school-class/EditClassNameWizard';
|
||||||
|
import EditTeamNameWizard from '@/components/profile/EditTeamNameWizard';
|
||||||
import FullscreenImage from '@/components/FullscreenImage';
|
import FullscreenImage from '@/components/FullscreenImage';
|
||||||
import FullscreenInfographic from '@/components/FullscreenInfographic';
|
import FullscreenInfographic from '@/components/FullscreenInfographic';
|
||||||
import FullscreenVideo from '@/components/FullscreenVideo';
|
import FullscreenVideo from '@/components/FullscreenVideo';
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
NewNoteWizard,
|
NewNoteWizard,
|
||||||
EditNoteWizard,
|
EditNoteWizard,
|
||||||
EditClassNameWizard,
|
EditClassNameWizard,
|
||||||
|
EditTeamNameWizard,
|
||||||
FullscreenImage,
|
FullscreenImage,
|
||||||
FullscreenInfographic,
|
FullscreenInfographic,
|
||||||
FullscreenVideo,
|
FullscreenVideo,
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="school-class">
|
|
||||||
<h2 class="school-class__heading"><span
|
|
||||||
class="school-class__name"
|
|
||||||
data-cy="school-class-name">{{ name }}</span>
|
|
||||||
<edit-class-name
|
|
||||||
v-if="teacher"
|
|
||||||
@edit="editClassName"/>
|
|
||||||
</h2>
|
|
||||||
<div class="school-class__members school-class-members">
|
|
||||||
<ul
|
|
||||||
class="school-class-members__list simple-list simple-list--active"
|
|
||||||
data-cy="active-class-members-list">
|
|
||||||
<li
|
|
||||||
:key="member.id"
|
|
||||||
class="simple-list__item member-item"
|
|
||||||
data-cy="school-class-member"
|
|
||||||
v-for="member in activeMembers">
|
|
||||||
<span class="member-item__name">{{ fullName(member) }}</span>
|
|
||||||
<span class="member-item__role">{{ role(member) }}</span>
|
|
||||||
<!-- <a-->
|
|
||||||
<!-- class="member-item__action simple-list__action"-->
|
|
||||||
<!-- data-cy="remove-from-class"-->
|
|
||||||
<!-- v-if="teacher"-->
|
|
||||||
<!-- @click="$emit('remove', member)">Deaktivieren</a>-->
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<!-- <template v-if="inactiveMembers.length">-->
|
|
||||||
<!-- <h3 class="school-class__inactive-heading">Deaktivierte Personen</h3>-->
|
|
||||||
<!-- <ul data-cy="inactive-class-members-list" class="simple-list simple-list--inactive">-->
|
|
||||||
<!-- <li-->
|
|
||||||
<!-- class="simple-list__item member-item"-->
|
|
||||||
<!-- data-cy="school-class-member"-->
|
|
||||||
<!-- v-for="member in inactiveMembers"-->
|
|
||||||
<!-- :key="member.id">-->
|
|
||||||
<!-- <span class="member-item__name">{{fullName(member)}}</span>-->
|
|
||||||
<!-- <span class="member-item__role">{{role(member)}}</span>-->
|
|
||||||
<!-- <a-->
|
|
||||||
<!-- class="member-item__action simple-list__action"-->
|
|
||||||
<!-- data-cy="add-to-class"-->
|
|
||||||
<!-- v-if="teacher"-->
|
|
||||||
<!-- @click="$emit('add', member)">Aktivieren</a>-->
|
|
||||||
<!-- </li>-->
|
|
||||||
<!-- </ul>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import EditClassName from '@/components/school-class/EditClassName';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['members', 'name', 'teacher', 'id'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
EditClassName
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
activeMembers() {
|
|
||||||
return this.members.filter(member => member.active);
|
|
||||||
},
|
|
||||||
inactiveMembers() {
|
|
||||||
return this.members.filter(member => !member.active);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
fullName(member) {
|
|
||||||
return `${member.firstName} ${member.lastName}`;
|
|
||||||
},
|
|
||||||
role({isTeacher}) {
|
|
||||||
return isTeacher ? 'Lehrperson' : 'Schüler';
|
|
||||||
},
|
|
||||||
editClassName() {
|
|
||||||
this.$store.dispatch('editClassName');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "@/styles/_variables.scss";
|
|
||||||
@import "@/styles/_mixins.scss";
|
|
||||||
|
|
||||||
.school-class {
|
|
||||||
&__inactive-heading {
|
|
||||||
@include heading-4;
|
|
||||||
margin-bottom: $small-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__name {
|
|
||||||
@include heading-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-item {
|
|
||||||
&__name {
|
|
||||||
font-family: $sans-serif-font-family;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
flex: 2 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__role {
|
|
||||||
flex: 0 1 110px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__action {
|
|
||||||
flex: 0 1 110px;
|
|
||||||
padding-left: $large-spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="edit-class-name"
|
class="edit-group-name"
|
||||||
data-cy="edit-class-name-link"
|
data-cy="edit-group-name-link"
|
||||||
@click="$emit('edit')">
|
@click="$emit('edit')">
|
||||||
<pen-icon class="edit-class-name__icon"/>
|
<pen-icon class="edit-group-name__icon"/>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "~styles/_variables.scss";
|
||||||
|
|
||||||
.edit-class-name {
|
.edit-group-name {
|
||||||
&__icon {
|
&__icon {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<modal
|
||||||
|
:hide-header="false"
|
||||||
|
:small="true">
|
||||||
|
<h4 slot="header">{{ type }} bearbeiten</h4>
|
||||||
|
<modal-input
|
||||||
|
:value="name"
|
||||||
|
placeholder="Klassenname"
|
||||||
|
data-cy="edit-name-input"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
/>
|
||||||
|
<div slot="footer">
|
||||||
|
<a
|
||||||
|
class="button button--primary"
|
||||||
|
data-cy="modal-save-button"
|
||||||
|
@click="$emit('save')">Speichern</a>
|
||||||
|
<a
|
||||||
|
class="button"
|
||||||
|
@click="$emit('cancel')">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import ModalInput from '@/components/ModalInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
ModalInput
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<edit-name-wizard
|
||||||
|
:name="name"
|
||||||
|
type="Team"
|
||||||
|
@input="name = $event"
|
||||||
|
@cancel="hideModal"
|
||||||
|
@save="save"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
import EditNameWizard from '@/components/profile/EditNameWizard';
|
||||||
|
import UPDATE_TEAM_MUTATION from '@/graphql/gql/mutations/updateTeam.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [me],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
EditNameWizard,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
team() {
|
||||||
|
return this.me.team;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.name = this.team ? this.team.name : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
save() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: UPDATE_TEAM_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name: this.name,
|
||||||
|
id: this.team.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(store, {data: {updateTeam: {team: {name}}}}) {
|
||||||
|
const query = ME_QUERY;
|
||||||
|
const data = store.readQuery({query});
|
||||||
|
data.me.team.name = name;
|
||||||
|
store.writeQuery({query, data});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.hideModal();
|
||||||
|
},
|
||||||
|
hideModal() {
|
||||||
|
this.$store.dispatch('hideModal');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<template>
|
||||||
|
<div class="group-list">
|
||||||
|
<h1
|
||||||
|
class="group-list__header"
|
||||||
|
data-cy="group-list-title">{{ title }}</h1>
|
||||||
|
<router-link
|
||||||
|
:to="showCodeRoute"
|
||||||
|
class="group-list__code-link button button--primary"
|
||||||
|
v-if="showCode">Zugangscode anzeigen
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="group-list__content">
|
||||||
|
<h2 class="group-list__heading"><span
|
||||||
|
class="group-list__name"
|
||||||
|
data-cy="group-list-name">{{ name }}</span>
|
||||||
|
<edit-group-name
|
||||||
|
v-if="canEdit"
|
||||||
|
@edit="$emit('edit')"/>
|
||||||
|
</h2>
|
||||||
|
<div class="group-list__members group-list-members">
|
||||||
|
<ul
|
||||||
|
class="group-list-members__list simple-list simple-list--active"
|
||||||
|
data-cy="active-class-members-list">
|
||||||
|
<li
|
||||||
|
:key="member.id"
|
||||||
|
class="simple-list__item member-item"
|
||||||
|
data-cy="group-list-member"
|
||||||
|
v-for="member in activeMembers">
|
||||||
|
<span class="member-item__name">{{ fullName(member) }}</span>
|
||||||
|
<span class="member-item__role">{{ role(member) }}</span>
|
||||||
|
<!-- <a-->
|
||||||
|
<!-- class="member-item__action simple-list__action"-->
|
||||||
|
<!-- data-cy="remove-from-class"-->
|
||||||
|
<!-- v-if="teacher"-->
|
||||||
|
<!-- @click="$emit('remove', member)">Deaktivieren</a>-->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- <template v-if="inactiveMembers.length">-->
|
||||||
|
<!-- <h3 class="group-list__inactive-heading">Deaktivierte Personen</h3>-->
|
||||||
|
<!-- <ul data-cy="inactive-class-members-list" class="simple-list simple-list--inactive">-->
|
||||||
|
<!-- <li-->
|
||||||
|
<!-- class="simple-list__item member-item"-->
|
||||||
|
<!-- data-cy="group-list-member"-->
|
||||||
|
<!-- v-for="member in inactiveMembers"-->
|
||||||
|
<!-- :key="member.id">-->
|
||||||
|
<!-- <span class="member-item__name">{{fullName(member)}}</span>-->
|
||||||
|
<!-- <span class="member-item__role">{{role(member)}}</span>-->
|
||||||
|
<!-- <a-->
|
||||||
|
<!-- class="member-item__action simple-list__action"-->
|
||||||
|
<!-- data-cy="add-to-class"-->
|
||||||
|
<!-- v-if="teacher"-->
|
||||||
|
<!-- @click="$emit('add', member)">Aktivieren</a>-->
|
||||||
|
<!-- </li>-->
|
||||||
|
<!-- </ul>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import EditGroupName from '@/components/profile/EditGroupName';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// props: ['active-members', 'inactive-members', 'name', 'canEdit'],
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
showCode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showCodeRoute: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
activeMembers: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
inactiveMembers: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
canEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
EditGroupName,
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fullName(member) {
|
||||||
|
return `${member.firstName} ${member.lastName}`;
|
||||||
|
},
|
||||||
|
role({isTeacher}) {
|
||||||
|
if (isTeacher === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return isTeacher ? 'Lehrperson' : 'Schüler';
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~styles/helpers";
|
||||||
|
|
||||||
|
.group-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
grid-template-areas: "h b" "c c";
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
grid-area: h;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__code-link {
|
||||||
|
grid-area: b;
|
||||||
|
justify-self: end;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
grid-area: c;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__inactive-heading {
|
||||||
|
@include heading-4;
|
||||||
|
margin-bottom: $small-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
@include heading-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item {
|
||||||
|
&__name {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
flex: 2 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__role {
|
||||||
|
flex: 0 1 110px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__action {
|
||||||
|
flex: 0 1 110px;
|
||||||
|
padding-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 data-cy="join-form-title">{{ title }}</h1>
|
||||||
|
<div>
|
||||||
|
<div class="skillboxform-input">
|
||||||
|
<label
|
||||||
|
for="join-code"
|
||||||
|
class="skillboxform-input__label">{{ labelText }}</label>
|
||||||
|
<input
|
||||||
|
:class="{'skillboxform-input__input--error': error}"
|
||||||
|
:value="value"
|
||||||
|
class="skillbox-input skillboxform-input__input"
|
||||||
|
data-cy="input-form-code"
|
||||||
|
id="join-code"
|
||||||
|
@input="$emit('input', $event)">
|
||||||
|
<small
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
v-if="error"
|
||||||
|
>{{ error }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
class="button button--primary button--big"
|
||||||
|
data-cy="join-form-confirm"
|
||||||
|
@click="$emit('confirm', value)">{{ okText }}</a>
|
||||||
|
<button
|
||||||
|
class="button button--big"
|
||||||
|
data-cy="join-form-cancel"
|
||||||
|
@click="$emit('cancel')">{{ cancelText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
okText: {
|
||||||
|
type: String,
|
||||||
|
default: 'Klasse beitreten'
|
||||||
|
},
|
||||||
|
labelText: {
|
||||||
|
type: String,
|
||||||
|
default: 'Zugangscode eingeben'
|
||||||
|
},
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
default: 'Abmelden'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -19,6 +19,15 @@
|
||||||
class="profile-sidebar__link">Meine Aktivitäten
|
class="profile-sidebar__link">Meine Aktivitäten
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="profile-sidebar__item"
|
||||||
|
v-if="me.isTeacher"
|
||||||
|
@click="close">
|
||||||
|
<router-link
|
||||||
|
:to="myTeamPage"
|
||||||
|
class="profile-sidebar__link">Mein Team
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-sidebar__section">
|
<div class="profile-sidebar__section">
|
||||||
<div class="profile-sidebar__item">
|
<div class="profile-sidebar__item">
|
||||||
|
|
@ -56,31 +65,40 @@
|
||||||
|
|
||||||
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
|
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
|
||||||
|
|
||||||
import sidebarMixin from '@/mixins/sidebar';
|
import sidebar from '@/mixins/sidebar';
|
||||||
|
import me from '@/mixins/me';
|
||||||
import LogoutWidget from '@/components/LogoutWidget';
|
import LogoutWidget from '@/components/LogoutWidget';
|
||||||
|
import {MY_TEAM} from '@/router/me.names';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
mixins: [sidebarMixin],
|
mixins: [sidebar, me],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
LogoutWidget,
|
LogoutWidget,
|
||||||
ClassSelectionWidget,
|
ClassSelectionWidget,
|
||||||
ProfileWidget,
|
ProfileWidget,
|
||||||
Cross
|
Cross,
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
myTeamPage() {
|
||||||
|
return {
|
||||||
|
name: MY_TEAM,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.closeSidebar('profile');
|
this.closeSidebar('profile');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "~styles/helpers";
|
||||||
@import "@/styles/_mixins.scss";
|
|
||||||
|
|
||||||
$desktop-width: 333px;
|
$desktop-width: 333px;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div class="show-code">
|
||||||
|
<h2 class="show-code__title">Zugangscode {{ type }} {{ name }}</h2>
|
||||||
|
<h1 class="show-code__code">{{ code }}</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
.show-code {
|
||||||
|
&__title {
|
||||||
|
@include regular-text;
|
||||||
|
margin-bottom: 2*$large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__code {
|
||||||
|
font-size: toRem(120px);
|
||||||
|
letter-spacing: toRem(20px);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,39 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<modal
|
<edit-name-wizard
|
||||||
:hide-header="false"
|
:name="name"
|
||||||
:small="true"
|
type="Klasse"
|
||||||
title="Hello">
|
@input="name = $event"
|
||||||
<h4 slot="header">Klasse bearbeiten</h4>
|
@cancel="hideModal"
|
||||||
<modal-input
|
@save="save" />
|
||||||
:value="name"
|
|
||||||
placeholder="Klassenname"
|
|
||||||
data-cy="edit-class-name-input"
|
|
||||||
@input="name = $event"
|
|
||||||
/>
|
|
||||||
<div slot="footer">
|
|
||||||
<a
|
|
||||||
class="button button--primary"
|
|
||||||
data-cy="modal-save-button"
|
|
||||||
@click="save">Speichern</a>
|
|
||||||
<a
|
|
||||||
class="button"
|
|
||||||
@click="hide">Abbrechen</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</modal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Modal from '@/components/Modal';
|
|
||||||
import ModalInput from '@/components/ModalInput';
|
|
||||||
|
|
||||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
||||||
import UPDATE_SCHOOL_CLASS_MUTATION from '@/graphql/gql/mutations/updateSchoolClass.gql';
|
import UPDATE_SCHOOL_CLASS_MUTATION from '@/graphql/gql/mutations/updateSchoolClass.gql';
|
||||||
|
import EditNameWizard from '@/components/profile/EditNameWizard';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
EditNameWizard,
|
||||||
ModalInput
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -63,9 +44,9 @@
|
||||||
store.writeQuery({query, data});
|
store.writeQuery({query, data});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.hide();
|
this.hideModal();
|
||||||
},
|
},
|
||||||
hide() {
|
hideModal() {
|
||||||
this.$store.dispatch('hideModal');
|
this.$store.dispatch('hideModal');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,16 @@
|
||||||
query MeQuery {
|
query MeQuery {
|
||||||
me {
|
me {
|
||||||
...UserParts
|
...UserParts
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
id
|
||||||
|
members {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
isTeacher
|
isTeacher
|
||||||
permissions
|
permissions
|
||||||
onboardingVisited
|
onboardingVisited
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
mutation CreateTeamMutation($input: CreateTeamInput!) {
|
||||||
|
createTeam(input: $input) {
|
||||||
|
success
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
mutation JoinTeamMutation($input: JoinTeamInput!) {
|
||||||
|
joinTeam(input: $input) {
|
||||||
|
success
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
mutation UpdateTeam($input: UpdateTeamInput!) {
|
||||||
|
updateTeam(input: $input) {
|
||||||
|
success
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
addTeam(store, team) {
|
||||||
|
console.log('add team');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -10,6 +10,7 @@ export default {
|
||||||
permissions: [],
|
permissions: [],
|
||||||
schoolClasses: [],
|
schoolClasses: [],
|
||||||
isTeacher: false,
|
isTeacher: false,
|
||||||
|
team: null
|
||||||
},
|
},
|
||||||
showPopover: false,
|
showPopover: false,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="create-class">
|
<join-form
|
||||||
<h1 class="create-class__title">Klasse erfassen</h1>
|
:value="name"
|
||||||
|
class="create-class"
|
||||||
<div>
|
title="Klasse erfassen"
|
||||||
<div class="skillboxform-input">
|
ok-text="Klasse erfassen"
|
||||||
<label
|
label-text="Name"
|
||||||
for="class-name"
|
cancel-text="Abbrechen"
|
||||||
class="skillboxform-input__label">Name</label>
|
@input="updateName"
|
||||||
<input
|
@cancel="cancel"
|
||||||
:class="{'skillboxform-input__input--error': error}"
|
@confirm="createClass"
|
||||||
:value="name"
|
/>
|
||||||
class="skillbox-input skillboxform-input__input"
|
|
||||||
data-cy="input-class-name"
|
|
||||||
id="class-name"
|
|
||||||
@input="updateName">
|
|
||||||
<small
|
|
||||||
class="skillboxform-input__error"
|
|
||||||
data-cy="email-local-errors"
|
|
||||||
v-if="error"
|
|
||||||
>{{ error }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
class="button button--primary button--big"
|
|
||||||
data-cy="create-class"
|
|
||||||
@click="createClass(name)">Klasse
|
|
||||||
erfassen</a>
|
|
||||||
<a
|
|
||||||
class="button button--big"
|
|
||||||
data-cy="create-class-cancel"
|
|
||||||
@click="cancel">Abbrechen</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -43,12 +17,18 @@
|
||||||
|
|
||||||
import CREATE_CLASS_MUTATION from '@/graphql/gql/mutations/createClass.gql';
|
import CREATE_CLASS_MUTATION from '@/graphql/gql/mutations/createClass.gql';
|
||||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
|
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
|
||||||
|
import JoinForm from '@/components/profile/JoinForm';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [addSchoolClassMixin],
|
mixins: [addSchoolClassMixin],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
JoinForm
|
||||||
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
name: '',
|
name: '',
|
||||||
error: ''
|
error: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -62,24 +42,24 @@
|
||||||
mutation: CREATE_CLASS_MUTATION,
|
mutation: CREATE_CLASS_MUTATION,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
name
|
name,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update(store, {data: {createSchoolClass: {schoolClass}}}) {
|
update(store, {data: {createSchoolClass: {schoolClass}}}) {
|
||||||
self.addSchoolClass(store, schoolClass);
|
self.addSchoolClass(store, schoolClass);
|
||||||
self.$router.push({
|
self.$router.push({
|
||||||
name: 'my-class'
|
name: 'my-class',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
refetchQueries: [{
|
refetchQueries: [{
|
||||||
query: MY_SCHOOL_CLASS_QUERY
|
query: MY_SCHOOL_CLASS_QUERY,
|
||||||
}]
|
}],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$router.go(-1);
|
this.$router.go(-1);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<join-form
|
||||||
<h1 data-cy="join-class-title">Einer Klasse beitreten</h1>
|
:value="code"
|
||||||
<div>
|
:error="error"
|
||||||
<div class="skillboxform-input">
|
title="Einer Klasse beitreten"
|
||||||
<label
|
ok-text="Klasse beitreten"
|
||||||
for="join-code"
|
cancel-text="Abmelden"
|
||||||
class="skillboxform-input__label">Zugangscode eingeben</label>
|
@input="updateCode"
|
||||||
<input
|
@cancel="logout"
|
||||||
:class="{'skillboxform-input__input--error': error}"
|
@confirm="joinClass"/>
|
||||||
:value="code"
|
|
||||||
class="skillbox-input skillboxform-input__input"
|
|
||||||
data-cy="input-class-code"
|
|
||||||
id="join-code"
|
|
||||||
@input="updateCode">
|
|
||||||
<small
|
|
||||||
class="skillboxform-input__error"
|
|
||||||
data-cy="email-local-errors"
|
|
||||||
v-if="error"
|
|
||||||
>{{ error }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
class="button button--primary button--big"
|
|
||||||
data-cy="join-class"
|
|
||||||
@click="joinClass(code)">Klasse beitreten</a>
|
|
||||||
<button
|
|
||||||
class="button button--big"
|
|
||||||
data-cy="join-class-cancel"
|
|
||||||
@click="logout">Abmelden
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -44,12 +17,15 @@
|
||||||
import addSchoolClass from '@/mixins/add-school-class';
|
import addSchoolClass from '@/mixins/add-school-class';
|
||||||
import logout from '@/mixins/logout';
|
import logout from '@/mixins/logout';
|
||||||
|
|
||||||
|
import JoinForm from '@/components/profile/JoinForm';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [addSchoolClass, logout],
|
mixins: [addSchoolClass, logout],
|
||||||
|
components: {JoinForm},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
code: '',
|
code: '',
|
||||||
error: ''
|
error: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -60,18 +36,18 @@
|
||||||
joinClass(code) {
|
joinClass(code) {
|
||||||
let self = this;
|
let self = this;
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation: JOIN_CLASS_MUTATION,
|
mutation: JOIN_CLASS_MUTATION,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
code
|
code,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update(store, {data: {joinClass: {schoolClass}}}) {
|
update(store, {data: {joinClass: {schoolClass}}}) {
|
||||||
self.addSchoolClass(store, schoolClass);
|
self.addSchoolClass(store, schoolClass);
|
||||||
self.$router.push({name: 'my-class'});
|
self.$router.push({name: 'my-class'});
|
||||||
},
|
},
|
||||||
refetchQueries: [{query: MY_SCHOOL_CLASS_QUERY}]
|
refetchQueries: [{query: MY_SCHOOL_CLASS_QUERY}],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
@ -83,7 +59,7 @@
|
||||||
this.error = 'Dieser Zugangscode ist nicht gültig.';
|
this.error = 'Dieser Zugangscode ist nicht gültig.';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<join-form
|
||||||
|
:value="name"
|
||||||
|
class="create-team"
|
||||||
|
title="Team erfassen"
|
||||||
|
ok-text="Team erfassen"
|
||||||
|
label-text="Name"
|
||||||
|
cancel-text="Abbrechen"
|
||||||
|
@input="updateName"
|
||||||
|
@cancel="cancel"
|
||||||
|
@confirm="createTeam"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import addTeamMixin from '@/mixins/add-team';
|
||||||
|
|
||||||
|
import JoinForm from '@/components/profile/JoinForm';
|
||||||
|
|
||||||
|
import CREATE_TEAM_MUTATION from '@/graphql/gql/mutations/createTeam.gql';
|
||||||
|
import {MY_TEAM} from '@/router/me.names';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [addTeamMixin],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
JoinForm
|
||||||
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
name: '',
|
||||||
|
error: '',
|
||||||
|
}),
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateName(event) {
|
||||||
|
this.name = event.target.value;
|
||||||
|
this.error = '';
|
||||||
|
},
|
||||||
|
createTeam(name) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: CREATE_TEAM_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: (store, {data: {createTeam: {team}}}) => {
|
||||||
|
this.addTeam(store, team);
|
||||||
|
this.$router.push({
|
||||||
|
name: MY_TEAM,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.$router.go(-1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<join-form
|
||||||
|
:value="code"
|
||||||
|
:error="error"
|
||||||
|
title="Einem Team beitreten"
|
||||||
|
ok-text="Team beitreten"
|
||||||
|
cancel-text="Abbrechen"
|
||||||
|
@input="updateCode"
|
||||||
|
@cancel="cancel"
|
||||||
|
@confirm="joinTeam"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import JOIN_TEAM_MUTATION from '@/graphql/gql/mutations/joinTeam.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
|
import JoinForm from '@/components/profile/JoinForm';
|
||||||
|
import {MY_TEAM} from '@/router/me.names';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
JoinForm,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
code: '',
|
||||||
|
error: '',
|
||||||
|
teamRoute: {
|
||||||
|
name: MY_TEAM,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateCode(event) {
|
||||||
|
this.code = event.target.value;
|
||||||
|
this.error = '';
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.$router.push(this.teamRoute);
|
||||||
|
},
|
||||||
|
joinTeam(code) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: JOIN_TEAM_MUTATION,
|
||||||
|
variables: {
|
||||||
|
'input': {
|
||||||
|
'code': code,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: (store, {data: {joinTeam: {team}}}) => {
|
||||||
|
const query = ME_QUERY;
|
||||||
|
const data = store.readQuery({query});
|
||||||
|
store.writeQuery({
|
||||||
|
query,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
me: {
|
||||||
|
...data.me,
|
||||||
|
team: team,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.$router.push({name: MY_TEAM});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div class="my-team">
|
||||||
|
<template v-if="me.team">
|
||||||
|
<group-list
|
||||||
|
:active-members="me.team.members"
|
||||||
|
:can-edit="true"
|
||||||
|
:show-code-route="showCodeRoute"
|
||||||
|
:show-code="true"
|
||||||
|
:name="me.team.name"
|
||||||
|
title="Mein Team"
|
||||||
|
@edit="editTeamName"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h1 class="my-team__heading">Mein Team {{ me.team }}</h1>
|
||||||
|
<div class="my-team__section">
|
||||||
|
<h2 class="my-team__subheading">Willst du einem bestehenden Team beitreten?</h2>
|
||||||
|
<router-link
|
||||||
|
:to="joinTeamRoute"
|
||||||
|
class="button button--primary">Zugangscode eingeben
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="my-team__section">
|
||||||
|
<h2 class="my-team__subheading">Willst du ein neues Team erfassen?</h2>
|
||||||
|
<router-link
|
||||||
|
:to="createTeamRoute"
|
||||||
|
class="button button--primary">Team erfassen
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {CREATE_TEAM, JOIN_TEAM, SHOW_TEAM_CODE} from '@/router/me.names';
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
import GroupList from '@/components/profile/GroupList';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [me],
|
||||||
|
components: {GroupList},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
joinTeamRoute: {
|
||||||
|
name: JOIN_TEAM,
|
||||||
|
},
|
||||||
|
createTeamRoute: {
|
||||||
|
name: CREATE_TEAM,
|
||||||
|
},
|
||||||
|
showCodeRoute: {
|
||||||
|
name: SHOW_TEAM_CODE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
editTeamName() {
|
||||||
|
this.$store.dispatch('editTeamName');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
.my-team {
|
||||||
|
&__heading {
|
||||||
|
margin-bottom: $section-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section {
|
||||||
|
margin-bottom: $section-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subheading {
|
||||||
|
@include heading-3;
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<show-code
|
||||||
|
:name="me.selectedClass.name"
|
||||||
|
:code="me.selectedClass.code"
|
||||||
|
class="show-school-class-code"
|
||||||
|
type="Klasse"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import selectedClassMixin from '@/mixins/selected-class';
|
||||||
|
import ShowCode from '@/components/profile/ShowCode';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [selectedClassMixin],
|
||||||
|
components: {ShowCode},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<show-code
|
||||||
|
:name="me.team.name"
|
||||||
|
:code="me.team.code"
|
||||||
|
class="show-school-class-code"
|
||||||
|
type="Team"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
import ShowCode from '@/components/profile/ShowCode';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [me],
|
||||||
|
components: {ShowCode},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -1,37 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="my-class">
|
<div class="my-class">
|
||||||
<h1
|
<group-list
|
||||||
class="my-class__header"
|
|
||||||
data-cy="class-list-title">Klassenliste</h1>
|
|
||||||
<router-link
|
|
||||||
:to="{name: 'show-code'}"
|
|
||||||
class="my-class__code-link button button--primary"
|
|
||||||
v-if="me.isTeacher">Zugangscode anzeigen
|
|
||||||
</router-link>
|
|
||||||
<class-list
|
|
||||||
:name="me.selectedClass.name"
|
:name="me.selectedClass.name"
|
||||||
:members="me.selectedClass.members"
|
:active-members="me.selectedClass.members.filter(member => member.active)"
|
||||||
:teacher="me.isTeacher"
|
:inactive-members="me.selectedClass.members.filter(member => !member.active)"
|
||||||
:id="me.selectedClass.id"
|
:show-code="me.isTeacher"
|
||||||
|
:show-code-route="showCodeRoute"
|
||||||
|
:can-edit="me.isTeacher"
|
||||||
|
title="Klassenliste"
|
||||||
class="my-class__class"
|
class="my-class__class"
|
||||||
@remove="remove"
|
|
||||||
@add="add"
|
@add="add"
|
||||||
|
@edit="editClassName"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ClassList from '@/components/profile/ClassList';
|
import GroupList from '@/components/profile/GroupList';
|
||||||
|
|
||||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
||||||
import ADD_REMOVE_MEMBER_MUTATION from '@/graphql/gql/mutations/addRemoveMember.gql';
|
import ADD_REMOVE_MEMBER_MUTATION from '@/graphql/gql/mutations/addRemoveMember.gql';
|
||||||
import selectedClassMixin from '@/mixins/selected-class';
|
import selectedClassMixin from '@/mixins/selected-class';
|
||||||
|
import {SHOW_SCHOOL_CLASS_CODE} from '@/router/me.names';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [selectedClassMixin],
|
mixins: [selectedClassMixin],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ClassList
|
GroupList,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showCodeRoute: {
|
||||||
|
name: SHOW_SCHOOL_CLASS_CODE,
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -42,8 +46,8 @@
|
||||||
input: {
|
input: {
|
||||||
member: member.id,
|
member: member.id,
|
||||||
schoolClass: this.me.selectedClass.id,
|
schoolClass: this.me.selectedClass.id,
|
||||||
active
|
active,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update(store, {data: {addRemoveMember: {success}}}) {
|
update(store, {data: {addRemoveMember: {success}}}) {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
@ -57,7 +61,7 @@
|
||||||
];
|
];
|
||||||
store.writeQuery({query, data});
|
store.writeQuery({query, data});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
add(member) {
|
add(member) {
|
||||||
|
|
@ -65,22 +69,25 @@
|
||||||
},
|
},
|
||||||
remove(member) {
|
remove(member) {
|
||||||
this.$modal.open('deactivate-person', {
|
this.$modal.open('deactivate-person', {
|
||||||
myself: member.id === this.me.id,
|
myself: member.id === this.me.id,
|
||||||
name: `${member.firstName} ${member.lastName}`,
|
name: `${member.firstName} ${member.lastName}`,
|
||||||
className: this.me.selectedClass.name,
|
className: this.me.selectedClass.name,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.changeMember(member, false);
|
this.changeMember(member, false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
editClassName() {
|
||||||
|
this.$store.dispatch('editClassName');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "~styles/helpers";
|
||||||
|
|
||||||
.my-class {
|
.my-class {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "~styles/helpers";
|
||||||
@import "@/styles/_functions.scss";
|
|
||||||
@import "@/styles/_mixins.scss";
|
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
padding: $large-spacing;
|
padding: $large-spacing;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="show-code">
|
|
||||||
<h2 class="show-code__title">Zugangscode Klasse {{ me.selectedClass.name }}</h2>
|
|
||||||
<h1 class="show-code__code">{{ me.selectedClass.code }}</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import selectedClassMixin from '@/mixins/selected-class';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [selectedClassMixin],
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "@/styles/_variables.scss";
|
|
||||||
@import "@/styles/_mixins.scss";
|
|
||||||
|
|
||||||
.show-code {
|
|
||||||
&__title {
|
|
||||||
@include regular-text;
|
|
||||||
margin-bottom: 2*$large-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__code {
|
|
||||||
font-size: toRem(120px);
|
|
||||||
letter-spacing: toRem(20px);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -6,10 +6,6 @@ import instrumentOverview from '@/pages/instrumentOverview';
|
||||||
import p404 from '@/pages/p404';
|
import p404 from '@/pages/p404';
|
||||||
import start from '@/pages/start';
|
import start from '@/pages/start';
|
||||||
import submission from '@/pages/studentSubmission';
|
import submission from '@/pages/studentSubmission';
|
||||||
import profilePage from '@/pages/profile';
|
|
||||||
import profile from '@/components/profile/Profile';
|
|
||||||
import myClass from '@/pages/myClass';
|
|
||||||
import activity from '@/pages/activity';
|
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import surveyPage from '@/pages/survey';
|
import surveyPage from '@/pages/survey';
|
||||||
import styleGuidePage from '@/pages/styleguide';
|
import styleGuidePage from '@/pages/styleguide';
|
||||||
|
|
@ -18,14 +14,12 @@ import emailVerification from '@/pages/email-verification';
|
||||||
import licenseActivation from '@/pages/license-activation';
|
import licenseActivation from '@/pages/license-activation';
|
||||||
import forgotPassword from '@/pages/forgot-password';
|
import forgotPassword from '@/pages/forgot-password';
|
||||||
import joinClass from '@/pages/joinClass';
|
import joinClass from '@/pages/joinClass';
|
||||||
import oldClasses from '@/pages/oldClasses';
|
|
||||||
import createClass from '@/pages/createClass';
|
|
||||||
import showCode from '@/pages/showCode';
|
|
||||||
import news from '@/pages/news';
|
import news from '@/pages/news';
|
||||||
|
|
||||||
import moduleRoutes from './module.routes';
|
import moduleRoutes from './module.routes';
|
||||||
import portfolioRoutes from './portfolio.routes';
|
import portfolioRoutes from './portfolio.routes';
|
||||||
import onboardingRoutes from './onboarding.routes';
|
import onboardingRoutes from './onboarding.routes';
|
||||||
|
import meRoutes from './me.routes';
|
||||||
import authRoutes from './auth.routes';
|
import authRoutes from './auth.routes';
|
||||||
import roomRoutes from './room.routes';
|
import roomRoutes from './room.routes';
|
||||||
|
|
||||||
|
|
@ -50,25 +44,8 @@ const routes = [
|
||||||
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
|
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
|
||||||
...portfolioRoutes,
|
...portfolioRoutes,
|
||||||
{path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug'},
|
{path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug'},
|
||||||
{
|
...meRoutes,
|
||||||
path: '/me',
|
{path: '/join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
||||||
component: profilePage,
|
|
||||||
children: [
|
|
||||||
{path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}},
|
|
||||||
{path: 'my-class', name: 'my-class', component: myClass, meta: {isProfile: true}},
|
|
||||||
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
|
||||||
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
|
||||||
{
|
|
||||||
path: 'old-classes',
|
|
||||||
name: 'old-classes',
|
|
||||||
component: oldClasses,
|
|
||||||
meta: {isProfile: true},
|
|
||||||
},
|
|
||||||
{path: 'create-class', name: 'create-class', component: createClass, meta: {layout: 'simple'}},
|
|
||||||
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'public'}},
|
|
||||||
{
|
{
|
||||||
path: '/survey/:id',
|
path: '/survey/:id',
|
||||||
component: surveyPage,
|
component: surveyPage,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const MY_TEAM = 'my-team';
|
||||||
|
export const JOIN_TEAM = 'join-team';
|
||||||
|
export const CREATE_TEAM = 'create-team';
|
||||||
|
export const SHOW_SCHOOL_CLASS_CODE = 'show-school-class-code';
|
||||||
|
export const SHOW_TEAM_CODE = 'show-teams-code';
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import profilePage from '@/pages/profile';
|
||||||
|
import profile from '@/components/profile/Profile';
|
||||||
|
import myClass from '@/pages/myClass';
|
||||||
|
import activity from '@/pages/activity';
|
||||||
|
import oldClasses from '@/pages/oldClasses';
|
||||||
|
import createClass from '@/pages/createClass';
|
||||||
|
import showSchoolClassCode from '@/pages/me/showSchoolClassCode';
|
||||||
|
import showTeamCode from '@/pages/me/showTeamCode';
|
||||||
|
import myTeam from '@/pages/me/myTeam';
|
||||||
|
import joinTeam from '@/pages/me/joinTeam';
|
||||||
|
import createTeam from '@/pages/me/createTeam';
|
||||||
|
|
||||||
|
import {CREATE_TEAM, JOIN_TEAM, MY_TEAM, SHOW_SCHOOL_CLASS_CODE, SHOW_TEAM_CODE} from './me.names';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/me',
|
||||||
|
component: profilePage,
|
||||||
|
children: [
|
||||||
|
{path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}},
|
||||||
|
{path: 'class', alias: 'my-class', name: 'my-class', component: myClass, meta: {isProfile: true}},
|
||||||
|
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
||||||
|
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
||||||
|
{
|
||||||
|
path: 'old-classes',
|
||||||
|
name: 'old-classes',
|
||||||
|
component: oldClasses,
|
||||||
|
meta: {isProfile: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'class/create',
|
||||||
|
alias: 'create-class',
|
||||||
|
name: 'create-class',
|
||||||
|
component: createClass,
|
||||||
|
meta: {layout: 'simple'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'class/code',
|
||||||
|
alias: 'show-code',
|
||||||
|
name: SHOW_SCHOOL_CLASS_CODE,
|
||||||
|
component: showSchoolClassCode,
|
||||||
|
meta: {layout: 'simple'},
|
||||||
|
},
|
||||||
|
{path: 'team', name: MY_TEAM, component: myTeam, meta: {isProfile: true}},
|
||||||
|
{path: 'team/join', name: JOIN_TEAM, component: joinTeam, meta: {isProfile: true, layout: 'simple'}},
|
||||||
|
{path: 'team/create', name: CREATE_TEAM, component: createTeam, meta: {isProfile: true, layout: 'simple'}},
|
||||||
|
{
|
||||||
|
path: 'team/code',
|
||||||
|
name: SHOW_TEAM_CODE,
|
||||||
|
component: showTeamCode,
|
||||||
|
meta: {layout: 'simple'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -185,9 +185,12 @@ export default new Vuex.Store({
|
||||||
editModule({commit}, payload) {
|
editModule({commit}, payload) {
|
||||||
commit('setEditModule', payload);
|
commit('setEditModule', payload);
|
||||||
},
|
},
|
||||||
editClassName({dispatch}, payload) {
|
editClassName({dispatch}) {
|
||||||
dispatch('showModal', 'edit-class-name-wizard');
|
dispatch('showModal', 'edit-class-name-wizard');
|
||||||
},
|
},
|
||||||
|
editTeamName({dispatch}) {
|
||||||
|
dispatch('showModal', 'edit-team-name-wizard');
|
||||||
|
},
|
||||||
deactivateUser({commit, dispatch}, payload) {
|
deactivateUser({commit, dispatch}, payload) {
|
||||||
commit('setModulePayload', payload);
|
commit('setModulePayload', payload);
|
||||||
return dispatch('showModal', 'deactivate-person');
|
return dispatch('showModal', 'deactivate-person');
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,17 @@ type CreateSchoolClassPayload {
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CreateTeamInput {
|
||||||
|
name: String!
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTeamPayload {
|
||||||
|
success: Boolean
|
||||||
|
team: TeamNode
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
type CustomMutation {
|
type CustomMutation {
|
||||||
redeemCoupon(input: CouponInput!): CouponPayload
|
redeemCoupon(input: CouponInput!): CouponPayload
|
||||||
spellCheck(input: SpellCheckInput!): SpellCheckPayload
|
spellCheck(input: SpellCheckInput!): SpellCheckPayload
|
||||||
|
|
@ -365,6 +376,9 @@ type CustomMutation {
|
||||||
updateSchoolClass(input: UpdateSchoolClassInput!): UpdateSchoolClassPayload
|
updateSchoolClass(input: UpdateSchoolClassInput!): UpdateSchoolClassPayload
|
||||||
createSchoolClass(input: CreateSchoolClassInput!): CreateSchoolClassPayload
|
createSchoolClass(input: CreateSchoolClassInput!): CreateSchoolClassPayload
|
||||||
updateOnboardingProgress: UpdateOnboardingProgress
|
updateOnboardingProgress: UpdateOnboardingProgress
|
||||||
|
createTeam(input: CreateTeamInput!): CreateTeamPayload
|
||||||
|
joinTeam(input: JoinTeamInput!): JoinTeamPayload
|
||||||
|
updateTeam(input: UpdateTeamInput!): UpdateTeamPayload
|
||||||
addProject(input: AddProjectInput!): AddProjectPayload
|
addProject(input: AddProjectInput!): AddProjectPayload
|
||||||
updateProject(input: UpdateProjectInput!): UpdateProjectPayload
|
updateProject(input: UpdateProjectInput!): UpdateProjectPayload
|
||||||
deleteProject(input: DeleteProjectInput!): DeleteProjectPayload
|
deleteProject(input: DeleteProjectInput!): DeleteProjectPayload
|
||||||
|
|
@ -578,6 +592,17 @@ type JoinClassPayload {
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input JoinTeamInput {
|
||||||
|
code: String!
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type JoinTeamPayload {
|
||||||
|
success: Boolean
|
||||||
|
team: TeamNode
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
type Logout {
|
type Logout {
|
||||||
success: Boolean
|
success: Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -803,8 +828,8 @@ type SchoolClassNode implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
isDeleted: Boolean!
|
isDeleted: Boolean!
|
||||||
users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection!
|
|
||||||
code: String
|
code: String
|
||||||
|
users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection!
|
||||||
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
|
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
|
||||||
hiddenChapterTitles(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
hiddenChapterTitles(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
||||||
hiddenChapterDescriptions(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
hiddenChapterDescriptions(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
||||||
|
|
@ -920,6 +945,16 @@ type SyncModuleVisibilityPayload {
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TeamNode implements Node {
|
||||||
|
name: String!
|
||||||
|
isDeleted: Boolean!
|
||||||
|
code: String
|
||||||
|
id: ID!
|
||||||
|
creator: UserNode
|
||||||
|
members: [UserNode]
|
||||||
|
pk: Int
|
||||||
|
}
|
||||||
|
|
||||||
type TopicConnection {
|
type TopicConnection {
|
||||||
pageInfo: PageInfo!
|
pageInfo: PageInfo!
|
||||||
edges: [TopicEdge]!
|
edges: [TopicEdge]!
|
||||||
|
|
@ -1266,6 +1301,18 @@ type UpdateSubmissionFeedbackPayload {
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UpdateTeamInput {
|
||||||
|
id: ID!
|
||||||
|
name: String
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTeamPayload {
|
||||||
|
success: Boolean
|
||||||
|
team: TeamNode
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
input UserGroupBlockVisibility {
|
input UserGroupBlockVisibility {
|
||||||
schoolClassId: ID!
|
schoolClassId: ID!
|
||||||
hidden: Boolean!
|
hidden: Boolean!
|
||||||
|
|
@ -1280,6 +1327,7 @@ type UserNode implements Node {
|
||||||
avatarUrl: String!
|
avatarUrl: String!
|
||||||
email: String!
|
email: String!
|
||||||
onboardingVisited: Boolean!
|
onboardingVisited: Boolean!
|
||||||
|
team: TeamNode
|
||||||
schoolClasses(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
|
schoolClasses(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
|
||||||
id: ID!
|
id: ID!
|
||||||
pk: Int
|
pk: Int
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from faker import Faker
|
||||||
from wagtail.documents.models import get_document_model
|
from wagtail.documents.models import get_document_model
|
||||||
from wagtail.images import get_image_model
|
from wagtail.images import get_image_model
|
||||||
|
|
||||||
|
from users.models import Role, UserRole
|
||||||
|
|
||||||
fake = Faker('de_CH')
|
fake = Faker('de_CH')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,7 +50,7 @@ class DummyImageFactory(factory.DjangoModelFactory):
|
||||||
class UserFactory(factory.django.DjangoModelFactory):
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = get_user_model()
|
model = get_user_model()
|
||||||
django_get_or_create = ('username', )
|
django_get_or_create = ('username',)
|
||||||
|
|
||||||
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
||||||
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
||||||
|
|
@ -58,3 +60,11 @@ class UserFactory(factory.django.DjangoModelFactory):
|
||||||
def post(self, create, extracted, **kwargs):
|
def post(self, create, extracted, **kwargs):
|
||||||
self.set_password('test')
|
self.set_password('test')
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class TeacherFactory(UserFactory):
|
||||||
|
@factory.post_generation
|
||||||
|
def post(self, create, extracted, **kwargs):
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
teacher_role = Role.objects.get_default_teacher_role()
|
||||||
|
UserRole.objects.create(user=self, role=teacher_role)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ class Command(BaseCommand):
|
||||||
with open(schema_path, 'w') as o:
|
with open(schema_path, 'w') as o:
|
||||||
o.write(str(schema))
|
o.write(str(schema))
|
||||||
|
|
||||||
with open(public_schema_path, 'w') as o:
|
# with open(public_schema_path, 'w') as o:
|
||||||
o.write(str(schema_public))
|
# o.write(str(schema_public))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import factory
|
import factory
|
||||||
from users.models import SchoolClass, SchoolClassMember, License
|
|
||||||
|
from users.models import SchoolClass, SchoolClassMember, License, Team
|
||||||
|
|
||||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
|
@ -31,6 +32,15 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
||||||
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TeamFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
|
||||||
|
name = factory.Faker('name')
|
||||||
|
code = factory.Sequence(lambda n: "CODE{}".format(n))
|
||||||
|
is_deleted = False
|
||||||
|
|
||||||
|
|
||||||
class LicenseFactory(factory.django.DjangoModelFactory):
|
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = License
|
model = License
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 2.2.19 on 2021-03-24 21:26
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0025_auto_20210126_1343'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Team',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('is_deleted', models.BooleanField(default=False)),
|
||||||
|
('code', models.CharField(blank=True, default=None, max_length=10, null=True, unique=True, verbose_name='Code zum Beitreten')),
|
||||||
|
('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='users.Team'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
import string
|
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import AbstractUser, Permission
|
from django.contrib.auth.models import AbstractUser, Permission
|
||||||
|
|
@ -25,6 +25,7 @@ class User(AbstractUser):
|
||||||
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
||||||
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
||||||
onboarding_visited = models.BooleanField(default=False)
|
onboarding_visited = models.BooleanField(default=False)
|
||||||
|
team = models.ForeignKey('users.Team', on_delete=models.SET_NULL, blank=True, null=True, related_name='members')
|
||||||
|
|
||||||
# for wagtail autocomplete
|
# for wagtail autocomplete
|
||||||
autocomplete_search_field = 'username'
|
autocomplete_search_field = 'username'
|
||||||
|
|
@ -110,14 +111,43 @@ class User(AbstractUser):
|
||||||
return self.get_full_name()
|
return self.get_full_name()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['pk',]
|
ordering = ['pk', ]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupWithCode(models.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
class SchoolClass(models.Model):
|
|
||||||
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
||||||
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
||||||
|
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||||
|
|
||||||
|
def generate_code(self):
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
digits = string.digits
|
||||||
|
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
||||||
|
try:
|
||||||
|
self.__class__.objects.get(code=code)
|
||||||
|
self.generate_code()
|
||||||
|
except self.__class__.DoesNotExist:
|
||||||
|
self.code = code.upper()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Team(GroupWithCode):
|
||||||
|
creator = models.ForeignKey(get_user_model(), null=True, on_delete=models.SET_NULL, blank=True, related_name='+')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Team'
|
||||||
|
verbose_name_plural = 'Teams'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolClass(GroupWithCode):
|
||||||
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
||||||
through='users.SchoolClassMember')
|
through='users.SchoolClassMember')
|
||||||
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Schulklasse'
|
verbose_name = 'Schulklasse'
|
||||||
|
|
@ -162,17 +192,6 @@ class SchoolClass(models.Model):
|
||||||
def get_teacher(self):
|
def get_teacher(self):
|
||||||
return self.users.filter(user_roles__role__key='teacher').first()
|
return self.users.filter(user_roles__role__key='teacher').first()
|
||||||
|
|
||||||
def generate_code(self):
|
|
||||||
letters = string.ascii_lowercase
|
|
||||||
digits = string.digits
|
|
||||||
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
|
||||||
try:
|
|
||||||
SchoolClass.objects.get(code=code)
|
|
||||||
self.generate_code()
|
|
||||||
except SchoolClass.DoesNotExist:
|
|
||||||
self.code = code.upper()
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.code == '': # '' can't be unique, so we null it
|
if self.code == '': # '' can't be unique, so we null it
|
||||||
self.code = None
|
self.code = None
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ from graphene import relay
|
||||||
from graphql_relay import from_global_id
|
from graphql_relay import from_global_id
|
||||||
|
|
||||||
from api.utils import get_object
|
from api.utils import get_object
|
||||||
|
from core.logger import get_logger
|
||||||
from users.inputs import PasswordUpdateInput
|
from users.inputs import PasswordUpdateInput
|
||||||
from users.models import SchoolClass, UserSetting, User, SchoolClassMember
|
from users.models import SchoolClass, SchoolClassMember, Team
|
||||||
from users.schema import SchoolClassNode
|
from users.schema import SchoolClassNode, TeamNode
|
||||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
class CodeNotFoundException(Exception):
|
class CodeNotFoundException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
@ -25,6 +27,18 @@ class UpdateError(graphene.ObjectType):
|
||||||
errors = graphene.List(FieldError)
|
errors = graphene.List(FieldError)
|
||||||
|
|
||||||
|
|
||||||
|
class TeacherOnlyMutation(relay.ClientIDMutation):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate(cls, root, info, input):
|
||||||
|
user = info.context.user
|
||||||
|
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
||||||
|
raise PermissionError('Permission denied')
|
||||||
|
return super().mutate(root, info, input)
|
||||||
|
|
||||||
|
|
||||||
class UpdatePassword(relay.ClientIDMutation):
|
class UpdatePassword(relay.ClientIDMutation):
|
||||||
class Input:
|
class Input:
|
||||||
password_input = graphene.Argument(PasswordUpdateInput)
|
password_input = graphene.Argument(PasswordUpdateInput)
|
||||||
|
|
@ -132,7 +146,7 @@ class JoinClass(relay.ClientIDMutation):
|
||||||
|
|
||||||
return cls(success=True, school_class=school_class)
|
return cls(success=True, school_class=school_class)
|
||||||
except SchoolClass.DoesNotExist:
|
except SchoolClass.DoesNotExist:
|
||||||
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid
|
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
|
||||||
|
|
||||||
|
|
||||||
class AddRemoveMember(relay.ClientIDMutation):
|
class AddRemoveMember(relay.ClientIDMutation):
|
||||||
|
|
@ -154,7 +168,7 @@ class AddRemoveMember(relay.ClientIDMutation):
|
||||||
school_class = get_object(SchoolClass, school_class_id)
|
school_class = get_object(SchoolClass, school_class_id)
|
||||||
|
|
||||||
if not user.is_teacher() or not school_class.users.filter(pk=user.pk).exists():
|
if not user.is_teacher() or not school_class.users.filter(pk=user.pk).exists():
|
||||||
raise PermissionError('Fehlende Berechtigung')
|
raise PermissionError('Permission denied')
|
||||||
|
|
||||||
school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
|
school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
|
||||||
school_class_member.active = active
|
school_class_member.active = active
|
||||||
|
|
@ -163,7 +177,7 @@ class AddRemoveMember(relay.ClientIDMutation):
|
||||||
return cls(success=True)
|
return cls(success=True)
|
||||||
|
|
||||||
|
|
||||||
class UpdateSchoolClass(relay.ClientIDMutation):
|
class UpdateSchoolClass(TeacherOnlyMutation):
|
||||||
class Input:
|
class Input:
|
||||||
id = graphene.ID(required=True)
|
id = graphene.ID(required=True)
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
|
|
@ -177,9 +191,7 @@ class UpdateSchoolClass(relay.ClientIDMutation):
|
||||||
name = kwargs.get('name')
|
name = kwargs.get('name')
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
|
|
||||||
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
# todo: only allow to edit your own school class
|
||||||
raise PermissionError()
|
|
||||||
|
|
||||||
school_class = get_object(SchoolClass, id)
|
school_class = get_object(SchoolClass, id)
|
||||||
school_class.name = name
|
school_class.name = name
|
||||||
school_class.save()
|
school_class.save()
|
||||||
|
|
@ -187,7 +199,7 @@ class UpdateSchoolClass(relay.ClientIDMutation):
|
||||||
return cls(success=True, school_class=school_class)
|
return cls(success=True, school_class=school_class)
|
||||||
|
|
||||||
|
|
||||||
class CreateSchoolClass(relay.ClientIDMutation):
|
class CreateSchoolClass(TeacherOnlyMutation):
|
||||||
class Input:
|
class Input:
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
|
|
||||||
|
|
@ -197,18 +209,78 @@ class CreateSchoolClass(relay.ClientIDMutation):
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
name = kwargs.get('name')
|
name = kwargs.get('name')
|
||||||
|
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
|
|
||||||
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
|
||||||
raise PermissionError()
|
|
||||||
|
|
||||||
school_class = SchoolClass.objects.create(name=name)
|
school_class = SchoolClass.objects.create(name=name)
|
||||||
SchoolClassMember.objects.create(school_class=school_class, user=user)
|
SchoolClassMember.objects.create(school_class=school_class, user=user)
|
||||||
user.set_selected_class(school_class)
|
user.set_selected_class(school_class)
|
||||||
return cls(success=True, school_class=school_class)
|
return cls(success=True, school_class=school_class)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateTeam(TeacherOnlyMutation):
|
||||||
|
class Input:
|
||||||
|
name = graphene.String(required=True)
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
name = kwargs.get('name')
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
team = Team.objects.create(name=name, creator=user)
|
||||||
|
team.generate_code()
|
||||||
|
user.team = team
|
||||||
|
user.save()
|
||||||
|
return cls(success=True, team=team)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateTeam(TeacherOnlyMutation):
|
||||||
|
class Input:
|
||||||
|
id = graphene.ID(required=True)
|
||||||
|
name = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
id = kwargs.get('id')
|
||||||
|
name = kwargs.get('name')
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
team = get_object(Team, id)
|
||||||
|
if user not in team.members.all():
|
||||||
|
logger.info('User not part of this team')
|
||||||
|
raise PermissionError('Permission denied')
|
||||||
|
team.name = name
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
return cls(success=True, team=team)
|
||||||
|
|
||||||
|
|
||||||
|
class JoinTeam(TeacherOnlyMutation):
|
||||||
|
class Input:
|
||||||
|
code = graphene.String(required=True)
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
user = info.context.user
|
||||||
|
code = kwargs.get('code')
|
||||||
|
try:
|
||||||
|
team = Team.objects.get(Q(code__iexact=code))
|
||||||
|
user.team = team
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return cls(success=True, team=team)
|
||||||
|
except Team.DoesNotExist:
|
||||||
|
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
|
||||||
|
|
||||||
|
|
||||||
class UpdateOnboardingProgress(graphene.Mutation):
|
class UpdateOnboardingProgress(graphene.Mutation):
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
|
|
||||||
|
|
@ -231,3 +303,6 @@ class ProfileMutations:
|
||||||
update_school_class = UpdateSchoolClass.Field()
|
update_school_class = UpdateSchoolClass.Field()
|
||||||
create_school_class = CreateSchoolClass.Field()
|
create_school_class = CreateSchoolClass.Field()
|
||||||
update_onboarding_progress = UpdateOnboardingProgress.Field()
|
update_onboarding_progress = UpdateOnboardingProgress.Field()
|
||||||
|
create_team = CreateTeam.Field()
|
||||||
|
join_team = JoinTeam.Field()
|
||||||
|
update_team = UpdateTeam.Field()
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ from datetime import datetime
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.dateformat import format
|
||||||
from django_filters import FilterSet, OrderingFilter
|
from django_filters import FilterSet, OrderingFilter
|
||||||
from graphene import relay, ObjectType
|
from graphene import relay, ObjectType
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
from django.utils.dateformat import format
|
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from basicknowledge.models import BasicKnowledge
|
from basicknowledge.models import BasicKnowledge
|
||||||
from basicknowledge.queries import InstrumentNode
|
from basicknowledge.queries import InstrumentNode
|
||||||
from books.models import Module, RecentModule
|
from books.models import Module
|
||||||
from books.schema.queries import ModuleNode
|
from books.schema.queries import ModuleNode
|
||||||
from users.models import User, SchoolClass, SchoolClassMember
|
from users.models import User, SchoolClass, SchoolClassMember, Team
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassNode(DjangoObjectType):
|
class SchoolClassNode(DjangoObjectType):
|
||||||
|
|
@ -40,6 +40,22 @@ class SchoolClassNode(DjangoObjectType):
|
||||||
return self.code
|
return self.code
|
||||||
|
|
||||||
|
|
||||||
|
class TeamNode(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
filter_fields = ['name']
|
||||||
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
|
pk = graphene.Int()
|
||||||
|
members = graphene.List('users.schema.UserNode')
|
||||||
|
|
||||||
|
def resolve_pk(self, *args, **kwargs):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def resolve_members(self, *args, **kwargs):
|
||||||
|
return self.members.all()
|
||||||
|
|
||||||
|
|
||||||
class RecentModuleFilter(FilterSet):
|
class RecentModuleFilter(FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
|
|
@ -60,13 +76,14 @@ class UserNode(DjangoObjectType):
|
||||||
is_teacher = graphene.Boolean()
|
is_teacher = graphene.Boolean()
|
||||||
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
||||||
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
|
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
filter_fields = ['username', 'email']
|
filter_fields = ['username', 'email']
|
||||||
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
|
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
|
||||||
'last_topic', 'avatar_url',
|
'last_topic', 'avatar_url',
|
||||||
'selected_class', 'expiry_date', 'onboarding_visited']
|
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
def resolve_pk(self, info, **kwargs):
|
def resolve_pk(self, info, **kwargs):
|
||||||
|
|
@ -100,6 +117,9 @@ class UserNode(DjangoObjectType):
|
||||||
# see https://docs.graphene-python.org/projects/django/en/latest/filtering/
|
# see https://docs.graphene-python.org/projects/django/en/latest/filtering/
|
||||||
return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self)
|
return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self)
|
||||||
|
|
||||||
|
def resolve_team(self, info, **kwargs):
|
||||||
|
return self.team
|
||||||
|
|
||||||
|
|
||||||
class ClassMemberNode(ObjectType):
|
class ClassMemberNode(ObjectType):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,16 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-10
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.conf import settings
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-04-09
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
|
from graphene import Context
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from api.utils import get_graphql_mutation, get_object
|
|
||||||
from core.factories import UserFactory
|
|
||||||
from users.models import SchoolClass, User
|
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
|
from api.utils import get_graphql_mutation, get_object
|
||||||
|
from core.factories import UserFactory, TeacherFactory
|
||||||
|
from users.models import SchoolClass, User
|
||||||
from users.services import create_users
|
from users.services import create_users
|
||||||
|
|
||||||
|
UPDATE_SCHOOL_CLASS_MUTATION = get_graphql_mutation('updateSchoolClass.gql')
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassesTest(TestCase):
|
class SchoolClassesTest(TestCase):
|
||||||
|
|
||||||
|
|
@ -53,6 +37,7 @@ class SchoolClassesTest(TestCase):
|
||||||
class_name = SchoolClass.generate_default_group_name(user=user)
|
class_name = SchoolClass.generate_default_group_name(user=user)
|
||||||
self.assertEqual(f'{self.prefix} {user.pk}', class_name)
|
self.assertEqual(f'{self.prefix} {user.pk}', class_name)
|
||||||
|
|
||||||
|
|
||||||
class ModifySchoolClassTest(TestCase):
|
class ModifySchoolClassTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_users()
|
create_users()
|
||||||
|
|
@ -72,9 +57,8 @@ class ModifySchoolClassTest(TestCase):
|
||||||
school_class = SchoolClass.objects.get(name='skillbox')
|
school_class = SchoolClass.objects.get(name='skillbox')
|
||||||
self.assertEqual(school_class.name, 'skillbox')
|
self.assertEqual(school_class.name, 'skillbox')
|
||||||
id = to_global_id('SchoolClassNode', school_class.id)
|
id = to_global_id('SchoolClassNode', school_class.id)
|
||||||
mutation = get_graphql_mutation('updateSchoolClass.gql')
|
|
||||||
|
|
||||||
result = self.client.execute(mutation, variables={
|
result = self.client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'id': id,
|
'id': id,
|
||||||
'name': class_name
|
'name': class_name
|
||||||
|
|
@ -85,15 +69,30 @@ class ModifySchoolClassTest(TestCase):
|
||||||
school_class = get_object(SchoolClass, id)
|
school_class = get_object(SchoolClass, id)
|
||||||
self.assertEqual(school_class.name, class_name)
|
self.assertEqual(school_class.name, class_name)
|
||||||
|
|
||||||
|
def test_update_school_class_not_in_class_fails(self):
|
||||||
|
client = Client(schema=schema)
|
||||||
|
teacher = TeacherFactory(username='conan')
|
||||||
|
context = Context(user=teacher)
|
||||||
|
school_class = SchoolClass.objects.get(name='skillbox')
|
||||||
|
self.assertEqual(school_class.name, 'skillbox')
|
||||||
|
id = to_global_id('SchoolClassNode', school_class.id)
|
||||||
|
variables = {
|
||||||
|
'input': {
|
||||||
|
'id': id,
|
||||||
|
'name': 'Nein'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context=context)
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
|
||||||
def test_update_school_class_fail(self):
|
def test_update_school_class_fail(self):
|
||||||
class_name = 'Nanana'
|
class_name = 'Nanana'
|
||||||
|
|
||||||
school_class = SchoolClass.objects.get(name='skillbox')
|
school_class = SchoolClass.objects.get(name='skillbox')
|
||||||
self.assertEqual(school_class.name, 'skillbox')
|
self.assertEqual(school_class.name, 'skillbox')
|
||||||
id = to_global_id('SchoolClassNode', school_class.id)
|
id = to_global_id('SchoolClassNode', school_class.id)
|
||||||
mutation = get_graphql_mutation('updateSchoolClass.gql')
|
|
||||||
|
|
||||||
result = self.student_client.execute(mutation, variables={
|
result = self.student_client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'id': id,
|
'id': id,
|
||||||
'name': class_name
|
'name': class_name
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from graphene import Context
|
||||||
|
from graphene.test import Client
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
|
from api.schema import schema
|
||||||
|
from api.utils import get_graphql_mutation
|
||||||
|
from core.factories import TeacherFactory, UserFactory
|
||||||
|
from users.factories import TeamFactory
|
||||||
|
from users.models import Team
|
||||||
|
|
||||||
|
ME_QUERY = """
|
||||||
|
query MeQuery {
|
||||||
|
me {
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_TEAM_MUTATION = get_graphql_mutation('createTeam.gql')
|
||||||
|
JOIN_TEAM_MUTATION = get_graphql_mutation('joinTeam.gql')
|
||||||
|
UPDATE_TEAM_MUTATION = get_graphql_mutation('updateTeam.gql')
|
||||||
|
|
||||||
|
|
||||||
|
class TeamTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client(schema=schema)
|
||||||
|
self.team_name = 'Fiterativ'
|
||||||
|
self.code = 'AAAA'
|
||||||
|
self.team = TeamFactory(name=self.team_name, code=self.code)
|
||||||
|
self.team_id = to_global_id('TeamNode', self.team.id)
|
||||||
|
self.user = TeacherFactory(username='ueli', team=self.team)
|
||||||
|
self.student = UserFactory(username='fritzli')
|
||||||
|
self.context = Context(user=self.user)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_team(result):
|
||||||
|
return result.get('data').get('me').get('team')
|
||||||
|
|
||||||
|
def no_error(self, result):
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
|
||||||
|
def permission_error(self, result):
|
||||||
|
errors = result.get('errors')
|
||||||
|
self.assertIsNotNone(errors)
|
||||||
|
self.assertIn('Permission denied', errors[0]['message'])
|
||||||
|
|
||||||
|
def check_me(self, context, name, code):
|
||||||
|
result = self.client.execute(ME_QUERY, context=context)
|
||||||
|
self.no_error(result)
|
||||||
|
team = self.get_team(result)
|
||||||
|
|
||||||
|
self.assertEqual(team.get('name'), name)
|
||||||
|
self.assertEqual(team.get('code'), code)
|
||||||
|
|
||||||
|
def join_team(self, code, context):
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"code": code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(JOIN_TEAM_MUTATION, variables=variables, context=context)
|
||||||
|
self.no_error(result)
|
||||||
|
success = result['data']['joinTeam']['success']
|
||||||
|
self.assertTrue(success)
|
||||||
|
|
||||||
|
def test_team_query(self):
|
||||||
|
self.check_me(self.context, self.team_name, self.code)
|
||||||
|
|
||||||
|
def test_join_team_mutation(self):
|
||||||
|
teacher = TeacherFactory(username='hansli')
|
||||||
|
context = Context(user=teacher)
|
||||||
|
self.join_team(self.code, context)
|
||||||
|
|
||||||
|
self.check_me(context, self.team_name, self.code)
|
||||||
|
|
||||||
|
def test_join_second_team_mutation(self):
|
||||||
|
teacher = TeacherFactory(username='peterli')
|
||||||
|
context = Context(user=teacher)
|
||||||
|
second_team = TeamFactory()
|
||||||
|
self.join_team(self.code, context)
|
||||||
|
self.check_me(context, self.team_name, self.code)
|
||||||
|
self.join_team(second_team.code, context)
|
||||||
|
self.check_me(context, second_team.name, second_team.code)
|
||||||
|
|
||||||
|
def test_create_team_mutation(self):
|
||||||
|
team_name = "Dunder Mifflin"
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": team_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(CREATE_TEAM_MUTATION, context=self.context, variables=variables)
|
||||||
|
self.no_error(result)
|
||||||
|
create_team = result.get('data').get('createTeam')
|
||||||
|
team = create_team.get('team')
|
||||||
|
success = create_team.get('success')
|
||||||
|
self.assertTrue(success)
|
||||||
|
self.assertEqual(team.get('name'), team_name)
|
||||||
|
self.assertIsNotNone(team.get('code'))
|
||||||
|
|
||||||
|
def test_update_team_name(self):
|
||||||
|
new_name = 'Team Böhmermann'
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": new_name,
|
||||||
|
"id": self.team_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(UPDATE_TEAM_MUTATION, context=self.context, variables=variables)
|
||||||
|
self.no_error(result)
|
||||||
|
update_team = result.get('data').get('updateTeam')
|
||||||
|
team = update_team.get('team')
|
||||||
|
success = update_team.get('success')
|
||||||
|
self.assertTrue(success)
|
||||||
|
self.assertEqual(team.get('name'), new_name)
|
||||||
|
|
||||||
|
def test_update_team_name_as_student_fails(self):
|
||||||
|
context = Context(user=self.student)
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": 'Not gonna happen',
|
||||||
|
"id": self.team_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(UPDATE_TEAM_MUTATION, context=context, variables=variables)
|
||||||
|
self.permission_error(result)
|
||||||
|
|
||||||
|
def test_update_team_name_not_in_team_fails(self):
|
||||||
|
schelm = TeacherFactory(username='schelm')
|
||||||
|
context = Context(user=schelm)
|
||||||
|
team = Team.objects.get(pk=self.team.pk)
|
||||||
|
old_name = team.name
|
||||||
|
self.assertFalse(self.team.members.filter(pk=schelm.pk).exists())
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": 'Not gonna happen',
|
||||||
|
"id": self.team_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(UPDATE_TEAM_MUTATION, context=context, variables=variables)
|
||||||
|
self.permission_error(result)
|
||||||
|
team = Team.objects.get(pk=self.team.pk)
|
||||||
|
self.assertEqual(team.name, old_name)
|
||||||
|
|
||||||
|
def test_create_team_mutation_as_student_fails(self):
|
||||||
|
self.assertEqual(Team.objects.count(), 1)
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": 'Nope'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context = Context(user=self.student)
|
||||||
|
result = self.client.execute(CREATE_TEAM_MUTATION, context=context, variables=variables)
|
||||||
|
self.permission_error(result)
|
||||||
|
self.assertEqual(Team.objects.count(), 1)
|
||||||
|
|
||||||
|
def test_join_create_team_mutation_as_student_fails(self):
|
||||||
|
self.assertIsNone(self.student.team)
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"code": 'ZZZZ'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context = Context(user=self.student)
|
||||||
|
result = self.client.execute(JOIN_TEAM_MUTATION, context=context, variables=variables)
|
||||||
|
self.permission_error(result)
|
||||||
|
self.assertIsNone(self.student.team)
|
||||||
Loading…
Reference in New Issue