Add visibility actions to objective group

This commit is contained in:
Ramon Wenger 2021-02-22 17:04:45 +01:00
parent 6bab4320ec
commit e10481ce49
16 changed files with 134 additions and 94 deletions

View File

@ -47,14 +47,11 @@
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue'; import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
import Chapter from '@/components/Chapter.vue'; import Chapter from '@/components/Chapter.vue';
import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql';
import UPDATE_LAST_MODULE_MUTATION from '@/graphql/gql/mutations/updateLastModule.gql'; import UPDATE_LAST_MODULE_MUTATION from '@/graphql/gql/mutations/updateLastModule.gql';
import UPDATE_MODULE_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateModuleBookmark.gql'; import UPDATE_MODULE_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateModuleBookmark.gql';
import OBJECTIVE_QUERY from '@/graphql/gql/objectiveQuery.gql';
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import ME_QUERY from '@/graphql/gql/meQuery.gql';
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql'; import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
import {withoutOwnerFirst} from '@/helpers/sorting';
import BookmarkActions from '@/components/notes/BookmarkActions'; import BookmarkActions from '@/components/notes/BookmarkActions';
import meMixin from '@/mixins/me'; import meMixin from '@/mixins/me';
@ -82,18 +79,15 @@
computed: { computed: {
languageCommunicationObjectiveGroups() { languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'LANGUAGE_COMMUNICATION') .filter(group => group.title === 'LANGUAGE_COMMUNICATION') : [];
.sort(withoutOwnerFirst) : [];
}, },
societyObjectiveGroups() { societyObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'SOCIETY') .filter(group => group.title === 'SOCIETY') : [];
.sort(withoutOwnerFirst) : [];
}, },
interdisciplinaryObjectiveGroups() { interdisciplinaryObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'INTERDISCIPLINARY') .filter(group => group.title === 'INTERDISCIPLINARY') : [];
.sort(withoutOwnerFirst) : [];
}, },
isStudent() { isStudent() {
return !this.me.permissions.includes('users.can_manage_school_class_content'); return !this.me.permissions.includes('users.can_manage_school_class_content');
@ -145,28 +139,6 @@
} }
}); });
}, },
updateObjectiveProgress(done, objectiveId) {
this.$apollo.mutate({
mutation: UPDATE_OBJECTIVE_PROGRESS_MUTATION,
variables: {
input: {
id: objectiveId,
done: done
}
},
update(store, {data: {updateObjectiveProgress: {objective}}}) {
if (objective) {
const variables = {id: objectiveId};
const query = OBJECTIVE_QUERY;
const data = store.readQuery({query, variables});
if (data && data.objective.objectiveProgress.edges.length > 0) {
data.objective.objectiveProgress.edges[0].node.done = done;
}
store.writeQuery({query: OBJECTIVE_QUERY, data, variables});
}
}
});
},
bookmark(bookmarked) { bookmark(bookmarked) {
const slug = this.module.slug; const slug = this.module.slug;
this.$apollo.mutate({ this.$apollo.mutate({

View File

@ -5,6 +5,7 @@
v-if="editModule || !hidden"> v-if="editModule || !hidden">
<visibility-action <visibility-action
:block="objective" :block="objective"
:type="type"
v-if="editModule"/> v-if="editModule"/>
<div <div
class="block-actions" class="block-actions"
@ -28,16 +29,19 @@
import UserWidget from '@/components/UserWidget'; import UserWidget from '@/components/UserWidget';
import MoreOptionsWidget from '@/components/MoreOptionsWidget'; import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import {mapState} from 'vuex';
import {meQuery} from '@/graphql/queries';
import DELETE_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/deleteObjective.gql'; import DELETE_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/deleteObjective.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql'; import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import {hidden} from '@/helpers/visibility'; import {hidden} from '@/helpers/visibility';
import {OBJECTIVE_TYPE} from '@/consts/types'; import {OBJECTIVE_TYPE} from '@/consts/types';
import editModule from '@/mixins/edit-module';
import me from '@/mixins/me';
export default { export default {
props: ['objective', 'schoolClass'], props: ['objective', 'schoolClass'],
mixins: [me, editModule],
components: { components: {
MoreOptionsWidget, MoreOptionsWidget,
VisibilityAction, VisibilityAction,
@ -46,17 +50,16 @@
data() { data() {
return { return {
me: {} type: OBJECTIVE_TYPE
}; };
}, },
computed: { computed: {
...mapState(['editModule']),
hidden() { hidden() {
return hidden({ return hidden({
block: this.objective, block: this.objective,
schoolClass: this.schoolClass, schoolClass: this.schoolClass,
type: OBJECTIVE_TYPE type: this.type
}); });
}, },
canEdit() { canEdit() {
@ -94,17 +97,14 @@
// } // }
}); });
} }
}, }
apollo: {
me: meQuery
},
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.objective { .objective {
min-height: 50px; min-height: 50px;
&__user-widget { &__user-widget {
margin-right: 0; margin-right: 0;
} }

View File

@ -1,5 +1,12 @@
<template> <template>
<div class="objective-group"> <div
:class="{'hideable-element--greyed-out': hidden}"
class="objective-group hideable-element"
v-if="editModule || !hidden">
<visibility-action
:block="group"
:type="type"
v-if="editModule"/>
<h4>{{ group.displayTitle }}</h4> <h4>{{ group.displayTitle }}</h4>
@ -7,7 +14,7 @@
<objective <objective
:key="objective.id" :key="objective.id"
:objective="objective" :objective="objective"
:school-class="currentFilter" :school-class="schoolClass"
class="objective-group__objective" class="objective-group__objective"
v-for="objective in group.objectives"/> v-for="objective in group.objectives"/>
</ul> </ul>
@ -20,12 +27,14 @@
<script> <script>
import Objective from '@/components/objective-groups/Objective'; import Objective from '@/components/objective-groups/Objective';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import AddContentButton from '@/components/AddContentButton'; import AddContentButton from '@/components/AddContentButton';
import {mapState} from 'vuex';
import me from '@/mixins/me'; import me from '@/mixins/me';
import editModule from '@/mixins/edit-module';
import {OBJECTIVE_GROUP_TYPE} from '@/consts/types';
import {hidden} from '@/helpers/visibility';
export default { export default {
props: { props: {
@ -35,17 +44,27 @@
} }
}, },
mixins: [me], mixins: [me, editModule],
components: { components: {
AddContentButton, AddContentButton,
Objective Objective,
VisibilityAction
},
data() {
return {
type: OBJECTIVE_GROUP_TYPE
};
}, },
computed: { computed: {
...mapState(['editModule']), hidden() {
currentFilter() { return hidden({
return this.me.selectedClass; block: this.group,
schoolClass: this.schoolClass,
type: this.type
});
}, },
}, },
}; };

View File

@ -10,7 +10,6 @@
<script> <script>
import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup'; import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup';
import {meQuery} from '@/graphql/queries'; import {meQuery} from '@/graphql/queries';
import {withoutOwnerFirst} from '@/helpers/sorting';
export default { export default {
props: { props: {
@ -47,19 +46,13 @@
return this.groups; return this.groups;
} else { } else {
// todo: maybe this can be done a bit more elegantly // todo: maybe this can be done a bit more elegantly
const groups = [...this.groups].sort(withoutOwnerFirst); let groups = this.groups;
const objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array const objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array
const firstGroup = Object.assign({}, groups.shift(), {objectives}); const firstGroup = Object.assign({}, groups.shift(), {objectives});
return [firstGroup]; return [firstGroup];
} }
} }
},
methods: {
updateObjectiveProgress(checked, id) {
this.$emit('updateObjectiveProgress', checked, id);
}
} }
}; };
</script> </script>

View File

@ -1,5 +1,12 @@
export const CONTENT_TYPE = 'content'; export const CONTENT_TYPE = 'content';
export const OBJECTIVE_TYPE = 'objective'; export const OBJECTIVE_TYPE = 'objective';
export const OBJECTIVE_GROUP_TYPE = 'objective-group';
export const CHAPTER_TITLE_TYPE = 'chapter-title'; export const CHAPTER_TITLE_TYPE = 'chapter-title';
export const CHAPTER_DESCRIPTION_TYPE = 'chapter-description'; export const CHAPTER_DESCRIPTION_TYPE = 'chapter-description';
export const TYPES = [CONTENT_TYPE, OBJECTIVE_TYPE, CHAPTER_TITLE_TYPE, CHAPTER_DESCRIPTION_TYPE]; export const TYPES = [
CONTENT_TYPE,
OBJECTIVE_TYPE,
OBJECTIVE_GROUP_TYPE,
CHAPTER_TITLE_TYPE,
CHAPTER_DESCRIPTION_TYPE
];

View File

@ -2,10 +2,6 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
id id
title title
displayTitle displayTitle
mine
owner {
id
}
hiddenFor { hiddenFor {
edges { edges {
node { node {
@ -14,12 +10,4 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
} }
} }
} }
visibleFor {
edges {
node {
id
name
}
}
}
} }

View File

@ -19,12 +19,4 @@ fragment ObjectiveParts on ObjectiveNode {
} }
} }
} }
objectiveProgress {
edges {
node {
id
done
}
}
}
} }

View File

@ -0,0 +1,9 @@
#import "../fragments/objectiveGroupParts.gql"
mutation UpdateObjectiveGroupVisibility($input: UpdateObjectiveGroupVisibilityInput!) {
updateObjectiveGroupVisibility(input: $input) {
objectiveGroup {
...ObjectiveGroupParts
}
}
}

View File

@ -1,6 +1,13 @@
import {CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE, CONTENT_TYPE, OBJECTIVE_TYPE} from '@/consts/types'; import {
CHAPTER_DESCRIPTION_TYPE,
CHAPTER_TITLE_TYPE,
CONTENT_TYPE,
OBJECTIVE_GROUP_TYPE,
OBJECTIVE_TYPE
} from '@/consts/types';
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql'; import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql'; import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql'; import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql';
export const createVisibilityMutation = (type, id, visibility) => { export const createVisibilityMutation = (type, id, visibility) => {
@ -26,6 +33,15 @@ export const createVisibilityMutation = (type, id, visibility) => {
} }
}; };
break; break;
case OBJECTIVE_GROUP_TYPE:
mutation = UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION;
variables = {
input: {
id,
visibility
}
};
break;
case CHAPTER_TITLE_TYPE: case CHAPTER_TITLE_TYPE:
case CHAPTER_DESCRIPTION_TYPE: case CHAPTER_DESCRIPTION_TYPE:
mutation = UPDATE_CHAPTER_VISIBILITY_MUTATION; mutation = UPDATE_CHAPTER_VISIBILITY_MUTATION;
@ -67,6 +83,8 @@ export const hidden = ({
? !containsClass(visibleFor, schoolClass) ? !containsClass(visibleFor, schoolClass)
// otherwise, is it explicitly hidden for this school class? // otherwise, is it explicitly hidden for this school class?
: containsClass(hiddenFor, schoolClass); : containsClass(hiddenFor, schoolClass);
case OBJECTIVE_GROUP_TYPE:
return containsClass(hiddenFor, schoolClass);
case CHAPTER_TITLE_TYPE: case CHAPTER_TITLE_TYPE:
return containsClass(titleHiddenFor, schoolClass); return containsClass(titleHiddenFor, schoolClass);
case CHAPTER_DESCRIPTION_TYPE: case CHAPTER_DESCRIPTION_TYPE:

View File

@ -0,0 +1,7 @@
import {mapState} from 'vuex';
export default {
computed: {
...mapState(['editModule']),
}
};

View File

@ -183,9 +183,7 @@ class ModuleNode(DjangoObjectType):
def resolve_objective_groups(self, root, **kwargs): def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \ return self.objective_groups.all() \
.prefetch_related('hidden_for') \ .prefetch_related('hidden_for')
.prefetch_related('visible_for') \
.prefetch_related('objectives__objective_progress')
class RecentModuleNode(DjangoObjectType): class RecentModuleNode(DjangoObjectType):

View File

@ -4,10 +4,11 @@ from wagtail.core.models import Page
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
from books.models import Topic from books.models import Topic
@admin.register(ObjectiveGroup) @admin.register(ObjectiveGroup)
class ObjectiveGroupAdmin(admin.ModelAdmin): class ObjectiveGroupAdmin(admin.ModelAdmin):
list_display = ('title', 'module', 'owner') list_display = ('title', 'module')
list_filter = ('title', 'module', 'owner') list_filter = ('title', 'module')
@admin.register(Objective) @admin.register(Objective)

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.18 on 2021-02-22 15:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('objectives', '0012_auto_20210210_1109'),
]
operations = [
migrations.RemoveField(
model_name='objectivegroup',
name='owner',
),
migrations.RemoveField(
model_name='objectivegroup',
name='visible_for',
),
]

View File

@ -23,11 +23,8 @@ class ObjectiveGroup(models.Model):
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES, default=LANGUAGE_COMMUNICATION) title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES, default=LANGUAGE_COMMUNICATION)
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE, related_name='objective_groups') module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE, related_name='objective_groups')
# a user can define her own objectives, hence this optional param
owner = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.PROTECT)
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True) hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True)
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objective_groups', blank=True)
def __str__(self): def __str__(self):
return '{} - {}'.format(self.module, self.title) return '{} - {}'.format(self.module, self.title)

View File

@ -58,6 +58,26 @@ class UpdateObjectiveVisibility(relay.ClientIDMutation):
return cls(objective=objective) return cls(objective=objective)
class UpdateObjectiveGroupVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True, description='The ID of the objective group')
visibility = graphene.List(UserGroupBlockVisibility)
objective_group = graphene.Field(ObjectiveGroupNode)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
objective_group_id = kwargs.get('id')
visibility_list = kwargs.get('visibility')
objective_group = get_object(ObjectiveGroup, objective_group_id) # info.context.user = django user
if visibility_list is not None:
set_hidden_for(objective_group, visibility_list)
objective_group.save()
return cls(objective_group=objective_group)
class AddObjective(relay.ClientIDMutation): class AddObjective(relay.ClientIDMutation):
class Input: class Input:
objective = graphene.Argument(AddObjectiveArgument) objective = graphene.Argument(AddObjectiveArgument)
@ -104,7 +124,9 @@ class DeleteObjective(relay.ClientIDMutation):
class ObjectiveMutations: class ObjectiveMutations:
# todo: remove?
update_objective_progress = UpdateObjectiveProgress.Field() update_objective_progress = UpdateObjectiveProgress.Field()
update_objective_visibility = UpdateObjectiveVisibility.Field() update_objective_visibility = UpdateObjectiveVisibility.Field()
update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field()
add_objective = AddObjective.Field() add_objective = AddObjective.Field()
delete_objective = DeleteObjective.Field() delete_objective = DeleteObjective.Field()

View File

@ -10,7 +10,6 @@ from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
class ObjectiveGroupNode(DjangoObjectType): class ObjectiveGroupNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
display_title = graphene.String() display_title = graphene.String()
mine = graphene.Boolean()
class Meta: class Meta:
model = ObjectiveGroup model = ObjectiveGroup
@ -23,9 +22,6 @@ class ObjectiveGroupNode(DjangoObjectType):
def resolve_display_title(self, *args, **kwargs): def resolve_display_title(self, *args, **kwargs):
return self.get_title_display() return self.get_title_display()
def resolve_mine(self, info, **kwargs):
return self.owner is not None and self.owner.pk == info.context.user.pk
def resolve_objectives(self, info, **kwargs): def resolve_objectives(self, info, **kwargs):
user = info.context.user user = info.context.user
school_classes = user.school_classes.values_list('pk') school_classes = user.school_classes.values_list('pk')