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