From 9d342298c77599518006041aafccafbaaaa45d90 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 15 Jan 2023 12:27:01 +0100 Subject: [PATCH] Replace ApolloQuery queries by queries registered in component --- .../components/about/InstalledAppsList.vue | 61 +++--- .../assets/components/app/SidenavSearch.vue | 87 ++++----- .../notifications/NotificationList.vue | 150 ++++++++------- .../components/person/AvatarContent.vue | 60 +++--- .../components/person/PersonActions.vue | 174 +++++++++--------- .../components/person/PersonOverview.vue | 15 +- aleksis/core/assets/messages/en.json | 1 + aleksis/core/assets/plugins/aleksis.js | 3 +- aleksis/core/assets/routes.js | 2 +- aleksis/core/schema/__init__.py | 6 +- aleksis/core/util/core_helpers.py | 2 +- 11 files changed, 295 insertions(+), 266 deletions(-) diff --git a/aleksis/core/assets/components/about/InstalledAppsList.vue b/aleksis/core/assets/components/about/InstalledAppsList.vue index 1a2d8b80f..04f14d873 100644 --- a/aleksis/core/assets/components/about/InstalledAppsList.vue +++ b/aleksis/core/assets/components/about/InstalledAppsList.vue @@ -1,41 +1,44 @@ <!-- List of all installed AlekSIS apps, as discovered from the server --> <template> - <ApolloQuery :query="require('./installedApps.graphql')"> - <template #default="{ result: { error, data }, isLoading }"> - <v-row v-if="isLoading"> - <v-col - v-for="idx in 3" - :key="idx" - cols="12" - md="6" - lg="6" - xl="4" - class="d-flex align-stretch" - > - <v-card class="d-flex flex-column flex-grow-1 pa-4"> - <v-skeleton-loader - type="heading, actions, text@5" - ></v-skeleton-loader> - </v-card> - </v-col> - </v-row> - <v-row v-if="data.installedApps"> - <installed-app-card - v-for="app in data.installedApps" - :key="app.name" - :app="app" - /> - </v-row> - </template> - </ApolloQuery> + <div> + <v-row v-if="$apollo.queries.installedApps.loading"> + <v-col + v-for="idx in 3" + :key="idx" + cols="12" + md="6" + lg="6" + xl="4" + class="d-flex align-stretch" + > + <v-card class="d-flex flex-column flex-grow-1 pa-4"> + <v-skeleton-loader + type="heading, actions, text@5" + ></v-skeleton-loader> + </v-card> + </v-col> + </v-row> + <v-row v-if="installedApps"> + <installed-app-card + v-for="app in installedApps" + :key="app.name" + :app="app" + /> + </v-row> + </div> </template> <script> import InstalledAppCard from "./InstalledAppCard.vue"; - +import gqlInstalledApps from "./installedApps.graphql"; export default { name: "InstalledAppsList", components: { InstalledAppCard }, + apollo: { + installedApps: { + query: gqlInstalledApps, + }, + }, }; </script> diff --git a/aleksis/core/assets/components/app/SidenavSearch.vue b/aleksis/core/assets/components/app/SidenavSearch.vue index 25a4a5e53..281e5abb9 100644 --- a/aleksis/core/assets/components/app/SidenavSearch.vue +++ b/aleksis/core/assets/components/app/SidenavSearch.vue @@ -1,50 +1,42 @@ <template> - <ApolloQuery - :query="require('./searchSnippets.graphql')" - :variables="{ - q, - }" - :skip="!q" + <v-autocomplete + :prepend-icon="'mdi-magnify'" + append-icon="" + @click:prepend="$router.push(`/search/?q=${q}`)" + @keydown.enter="$router.push(`/search/?q=${q}`)" + single-line + clearable + :loading="$apollo.queries.searchSnippets.loading" + id="search" + type="search" + enterkeyhint="search" + :label="$t('actions.search')" + :search-input.sync="q" + flat + solo + cache-items + hide-no-data + hide-details + menu-props="closeOnContentClick" + :items="searchSnippets" > - <template #default="{ result: { error, data }, isLoading, query }"> - <v-autocomplete - :prepend-icon="'mdi-magnify'" - append-icon="" - @click:prepend="$router.push(`/search/?q=${q}`)" - @keydown.enter="$router.push(`/search/?q=${q}`)" - single-line - clearable - :loading="!!isLoading" - id="search" - type="search" - enterkeyhint="search" - :label="$t('actions.search')" - :search-input.sync="q" - flat - solo - cache-items - hide-no-data - hide-details - menu-props="closeOnContentClick" - :items="data ? data.searchSnippets : undefined" - > - <template #item="{ item }"> - <v-list-item @click="$router.push(item.obj.absoluteUrl.substring(7))"> - <v-list-item-icon v-if="item.obj.icon"> - <v-icon>{{ "mdi-" + item.obj.icon }}</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title> {{ item.obj.name }}</v-list-item-title> - <v-list-item-subtitle>{{ item.text }}</v-list-item-subtitle> - </v-list-item-content> - </v-list-item> - </template> - </v-autocomplete> + <template #item="{ item }"> + <v-list-item @click="$router.push(item.obj.absoluteUrl.substring(7))"> + <v-list-item-icon v-if="item.obj.icon"> + <v-icon>{{ "mdi-" + item.obj.icon }}</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> {{ item.obj.name }}</v-list-item-title> + <v-list-item-subtitle>{{ item.text }}</v-list-item-subtitle> + </v-list-item-content> + </v-list-item> </template> - </ApolloQuery> + </v-autocomplete> </template> <script> +import gqlSearchSnippets from "./searchSnippets.graphql"; + export default { name: "SidenavSearch", data() { @@ -52,5 +44,18 @@ export default { q: "", }; }, + apollo: { + searchSnippets: { + query: gqlSearchSnippets, + variables() { + return { + q: this.q, + }; + }, + skip() { + return !this.q; + }, + }, + }, }; </script> diff --git a/aleksis/core/assets/components/notifications/NotificationList.vue b/aleksis/core/assets/components/notifications/NotificationList.vue index c290cbef2..b64769cc6 100644 --- a/aleksis/core/assets/components/notifications/NotificationList.vue +++ b/aleksis/core/assets/components/notifications/NotificationList.vue @@ -1,92 +1,90 @@ <template> - <ApolloQuery - :query="require('./myNotifications.graphql')" - :poll-interval="1000" + <v-menu + offset-y + :close-on-content-click="false" + max-width="min(600px, 80vw)" + width="min-content" + max-height="90%" > - <template #default="{ result: { error, data, loading } }"> - <v-menu - offset-y - :close-on-content-click="false" - max-width="min(600px, 80vw)" - width="min-content" - max-height="90%" + <template #activator="{ on, attrs }"> + <v-btn + icon + color="primary" + v-bind="attrs" + v-on="on" + :loading="$apollo.queries.myNotifications.loading" + class="mx-2" > - <template #activator="{ on, attrs }"> - <v-btn - icon - color="primary" - v-bind="attrs" - v-on="on" - :loading="loading" - class="mx-2" - > - <v-icon - color="white" - v-if=" - data && - data.myNotifications.person && - data.myNotifications.person.unreadNotificationsCount > 0 - " - > - mdi-bell-badge-outline - </v-icon> - <v-icon color="white" v-else>mdi-bell-outline</v-icon> - </v-btn> - </template> - <v-skeleton-loader - v-if="loading" - class="mx-auto" - type="paragraph" - ></v-skeleton-loader> - <v-list v-else nav three-line dense class="overflow-y-auto"> - <template + <v-icon + color="white" + v-if=" + myNotifications && + myNotifications.person && + myNotifications.person.unreadNotificationsCount > 0 + " + > + mdi-bell-badge-outline + </v-icon> + <v-icon color="white" v-else>mdi-bell-outline</v-icon> + </v-btn> + </template> + <v-skeleton-loader + v-if="$apollo.queries.myNotifications.loading" + class="mx-auto" + type="paragraph" + ></v-skeleton-loader> + <v-list v-else nav three-line dense class="overflow-y-auto"> + <template + v-if=" + myNotifications.person && + myNotifications.person.notifications && + myNotifications.person.notifications.length + " + > + <v-subheader>{{ $t("notifications.notifications") }}</v-subheader> + <template v-for="notification in myNotifications.person.notifications"> + <NotificationItem + :key="notification.id" + :notification="notification" + /> + <v-divider v-if=" - data.myNotifications.person && - data.myNotifications.person.notifications && - data.myNotifications.person.notifications.length + notification !== + myNotifications.person.notifications[ + myNotifications.person.notifications.length - 1 + ] " - > - <v-subheader>{{ $t("notifications.notifications") }}</v-subheader> - <template - v-for="notification in data.myNotifications.person.notifications" - > - <NotificationItem - :key="notification.id" - :notification="notification" - /> - <v-divider - v-if=" - notification !== - data.myNotifications.person.notifications[ - data.myNotifications.person.notifications.length - 1 - ] - " - :key="notification.id + '-divider'" - ></v-divider> - </template> - </template> - <template v-else> - <v-list-item> - <div class="d-flex justify-center align-center flex-column"> - <div class="mb-4"> - <v-icon large color="primary">mdi-bell-off-outline</v-icon> - </div> - <div>{{ $t("notifications.no_notifications") }}</div> - </div> - </v-list-item> - </template> - </v-list> - </v-menu> - </template> - </ApolloQuery> + :key="notification.id + '-divider'" + ></v-divider> + </template> + </template> + <template v-else> + <v-list-item> + <div class="d-flex justify-center align-center flex-column"> + <div class="mb-4"> + <v-icon large color="primary">mdi-bell-off-outline</v-icon> + </div> + <div>{{ $t("notifications.no_notifications") }}</div> + </div> + </v-list-item> + </template> + </v-list> + </v-menu> </template> <script> import NotificationItem from "./NotificationItem.vue"; +import gqlMyNotifications from "./myNotifications.graphql"; export default { components: { NotificationItem, }, + apollo: { + myNotifications: { + query: gqlMyNotifications, + pollInterval: 1000, + }, + }, }; </script> diff --git a/aleksis/core/assets/components/person/AvatarContent.vue b/aleksis/core/assets/components/person/AvatarContent.vue index 5ca894ac8..db9ab41d7 100644 --- a/aleksis/core/assets/components/person/AvatarContent.vue +++ b/aleksis/core/assets/components/person/AvatarContent.vue @@ -1,35 +1,28 @@ <template> - <ApolloQuery - v-if="id" - :query="require('./avatarContent.graphql')" - :variables="{ id }" - class="fullsize" - > - <template #default="{ result: { error, data, loading } }"> - <template v-if="loading"> - <v-row class="fill-height ma-0" align="center" justify="center"> - <v-progress-circular - indeterminate - color="grey lighten-5" - ></v-progress-circular> - </v-row> - </template> - <v-img - v-if="data && data.person && data.person.image" - :src="data.person.image" - :alt="$t('person.avatar')" - max-width="100%" - max-height="100%" - :contain="contain" - class="fullsize" - /> - <v-icon class="grey lighten-1" dark v-else>mdi-folder</v-icon> + <div class="fullsize"> + <template v-if="$apollo.queries.person.loading"> + <v-row class="fill-height ma-0" align="center" justify="center"> + <v-progress-circular + indeterminate + color="grey lighten-5" + ></v-progress-circular> + </v-row> </template> - </ApolloQuery> - <v-icon class="grey lighten-1" dark v-else>mdi-folder</v-icon> + <v-img + v-if="person && person.image" + :src="person.image" + :alt="$t('person.avatar')" + max-width="100%" + max-height="100%" + :contain="contain" + class="fullsize" + /> + <v-icon class="grey lighten-1" dark v-else>mdi-folder</v-icon> + </div> </template> <script> +import gqlAvatarContent from "./avatarContent.graphql"; export default { name: "AvatarContent", props: { @@ -44,6 +37,19 @@ export default { default: false, }, }, + apollo: { + person: { + query: gqlAvatarContent, + variables() { + return { + id: this.id, + }; + }, + skip() { + return !this.id; + }, + }, + }, }; </script> diff --git a/aleksis/core/assets/components/person/PersonActions.vue b/aleksis/core/assets/components/person/PersonActions.vue index 3b99a4fdd..f6368d4e3 100644 --- a/aleksis/core/assets/components/person/PersonActions.vue +++ b/aleksis/core/assets/components/person/PersonActions.vue @@ -1,96 +1,96 @@ <template> - <ApolloQuery :query="require('./personActions.graphql')" :variables="{ id }"> - <template #default="{ result: { error, data, loading } }"> - <v-skeleton-loader v-if="loading" type="actions" /> - <template v-else-if="data && data.person && data.person.id"> - <v-btn - v-if="data.person.canEditPerson" - color="primary" - :to="{ name: 'core.editPerson', params: { id: data.person.id } }" - > - <v-icon left>$edit</v-icon> - {{ $t("actions.edit") }} - </v-btn> - <v-btn - v-if="data.person.canChangePersonPreferences" - color="secondary" - outlined - text + <div> + <v-skeleton-loader v-if="$apollo.queries.person.loading" type="actions" /> + <template v-else-if="person && person.id"> + <v-btn + v-if="person.canEditPerson" + color="primary" + :to="{ name: 'core.editPerson', params: { id: person.id } }" + > + <v-icon left>$edit</v-icon> + {{ $t("actions.edit") }} + </v-btn> + <v-btn + v-if="person.canChangePersonPreferences" + color="secondary" + outlined + text + :to="{ + name: 'core.preferencesPersonByPk', + params: { pk: person.id }, + }" + > + <v-icon left>$preferences</v-icon> + {{ $t("preferences.person.change_preferences") }} + </v-btn> + + <button-menu + v-if=" + person.canImpersonatePerson || + person.canInvitePerson || + person.canDeletePerson + " + > + <v-list-item + v-if="person.canImpersonatePerson" :to="{ - name: 'core.preferencesPersonByPk', - params: { pk: data.person.id }, + name: 'impersonate.impersonateByUserPk', + params: { uid: person.userid }, + query: { next: $route.path }, }" > - <v-icon left>$preferences</v-icon> - {{ $t("preferences.person.change_preferences") }} - </v-btn> + <v-list-item-icon> + <v-icon>mdi-account-box-outline</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + {{ $t("person.impersonation.impersonate") }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> - <button-menu - v-if=" - data.person.canImpersonatePerson || - data.person.canInvitePerson || - data.person.canDeletePerson - " + <v-list-item + v-if="person.canInvitePerson" + :to="{ + name: 'core.invitePerson', + params: { id: person.id }, + }" > - <v-list-item - v-if="data.person.canImpersonatePerson" - :to="{ - name: 'impersonate.impersonateByUserPk', - params: { uid: data.person.userid }, - query: { next: $route.path }, - }" - > - <v-list-item-icon> - <v-icon>mdi-account-box-outline</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title> - {{ $t("person.impersonation.impersonate") }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> - - <v-list-item - v-if="data.person.canInvitePerson" - :to="{ - name: 'core.invitePerson', - params: { id: data.person.id }, - }" - > - <v-list-item-icon> - <v-icon>mdi-account-plus-outline</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title> - {{ $t("person.invite") }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> + <v-list-item-icon> + <v-icon>mdi-account-plus-outline</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + {{ $t("person.invite") }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> - <v-list-item - v-if="data.person.canDeletePerson" - :to="{ - name: 'core.deletePerson', - params: { id: data.person.id }, - }" - class="error--text" - > - <v-list-item-icon> - <v-icon color="error">mdi-delete</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title> - {{ $t("person.delete") }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> - </button-menu> - </template> + <v-list-item + v-if="person.canDeletePerson" + :to="{ + name: 'core.deletePerson', + params: { id: person.id }, + }" + class="error--text" + > + <v-list-item-icon> + <v-icon color="error">mdi-delete</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + {{ $t("person.delete") }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + </button-menu> </template> - </ApolloQuery> + </div> </template> <script> +import gqlPersonActions from "./personActions.graphql"; + export default { name: "PersonActions", props: { @@ -99,6 +99,16 @@ export default { required: true, }, }, + apollo: { + person: { + query: gqlPersonActions, + variables() { + return { + id: this.id, + }; + }, + }, + }, }; </script> diff --git a/aleksis/core/assets/components/person/PersonOverview.vue b/aleksis/core/assets/components/person/PersonOverview.vue index 295065ab0..978b6689a 100644 --- a/aleksis/core/assets/components/person/PersonOverview.vue +++ b/aleksis/core/assets/components/person/PersonOverview.vue @@ -214,12 +214,17 @@ export default { person: { query: gqlPersonOverview, variables() { - return { - id: this.$route.params.id, - }; + if (this.$route.params.id) { + return { + id: this.$route.params.id, + }; + } + return {}; }, - result({ data: { person } }) { - this.$root.$setPageTitle(person.fullName); + result({ data }) { + if (data && data.person) { + this.$root.$setPageTitle(data.person.fullName); + } }, }, }, diff --git a/aleksis/core/assets/messages/en.json b/aleksis/core/assets/messages/en.json index 571895dd5..49f40d949 100644 --- a/aleksis/core/assets/messages/en.json +++ b/aleksis/core/assets/messages/en.json @@ -1,6 +1,7 @@ { "about": { "about_aleksis": "About AlekSIS®", + "page_title": "About AlekSIS®", "about_aleksis_1": "This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software and can be used by anyone.", "about_aleksis_2": "AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V.", "free_open_source_licence": "Free/Open Source Licence", diff --git a/aleksis/core/assets/plugins/aleksis.js b/aleksis/core/assets/plugins/aleksis.js index 2c91eebf9..4f6a960da 100644 --- a/aleksis/core/assets/plugins/aleksis.js +++ b/aleksis/core/assets/plugins/aleksis.js @@ -35,7 +35,8 @@ AleksisVue.install = function (Vue, options) { Vue, dsn: Vue.$aleksisFrontendSettings.sentry.dsn, environment: Vue.$aleksisFrontendSettings.sentry.environment, - tracesSampleRate: Vue.$aleksisFrontendSettings.sentry.traces_sample_rate, + tracesSampleRate: + Vue.$aleksisFrontendSettings.sentry.traces_sample_rate, logError: true, integrations: [ new BrowserTracing({ diff --git a/aleksis/core/assets/routes.js b/aleksis/core/assets/routes.js index 0a9572671..2732a7ba5 100644 --- a/aleksis/core/assets/routes.js +++ b/aleksis/core/assets/routes.js @@ -771,7 +771,7 @@ const routes = [ component: () => import("./components/about/About.vue"), name: "core.about", meta: { - titleKey: "about.pageTitle", + titleKey: "about.page_title", }, }, ]; diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py index a94f88d48..f7ab8487f 100644 --- a/aleksis/core/schema/__init__.py +++ b/aleksis/core/schema/__init__.py @@ -75,12 +75,12 @@ class Query(graphene.ObjectType): raise PermissionDenied() return person - def resolve_person_by_id_or_me(root, info, id): # noqa + def resolve_person_by_id_or_me(root, info, **kwargs): # noqa # Returns person associated with current user if id is None, else the person with the id - if id is None: + if "id" not in kwargs or kwargs["id"] is None: return info.context.user.person if has_person(info.context.user) else None - person = Person.objects.get(pk=id) + person = Person.objects.get(pk=kwargs["id"]) if not info.context.user.has_perm("core.view_person_rule", person): raise PermissionDenied() return person diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index d43f7fc78..511b6ada1 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -234,7 +234,7 @@ def custom_information_processor(request: Union[HttpRequest, None]) -> dict: "urls": { "base": settings.BASE_URL, "graphql": reverse("graphql"), - } + }, } context = { -- GitLab