fix: significantly reduce unnecessary GraphQL HTTP calls for config information

Also stops calling the heavy ABOUT GraphQL query on HomeView, which does not require the longDescription field.

Fixes #1598
This commit is contained in:
Massedil
2025-06-20 23:58:41 +02:00
parent 1c467099f0
commit 7ceb631518
14 changed files with 69 additions and 369 deletions

View File

@@ -95,7 +95,7 @@ import { useQuery, useQueryLoading } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { useHead } from "@/utils/head";
import { useAnalytics } from "@/composition/apollo/config";
import { INSTANCE_NAME } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
const SentryFeedback = defineAsyncComponent(
() => import("./Feedback/SentryFeedback.vue")
);
@@ -116,7 +116,7 @@ useHead({
});
const { result: instanceConfig } = useQuery<{ config: Pick<IConfig, "name"> }>(
INSTANCE_NAME
CONFIG
);
const instanceName = computed(() => instanceConfig.value?.config.name);

View File

@@ -1,28 +1,4 @@
import {
ABOUT,
ANALYTICS,
ANONYMOUS_ACTOR_ID,
ANONYMOUS_PARTICIPATION_CONFIG,
ANONYMOUS_REPORTS_CONFIG,
DEFAULT_PICTURE,
DEMO_MODE,
LONG_EVENTS,
EVENT_CATEGORIES,
EVENT_PARTICIPANTS,
FEATURES,
GEOCODING_AUTOCOMPLETE,
COLORS,
INSTANCE_LOGO,
LOCATION,
MAPS_TILES,
REGISTRATIONS,
RESOURCE_PROVIDERS,
RESTRICTIONS,
ROUTING_TYPE,
SEARCH_CONFIG,
TIMEZONES,
UPLOAD_LIMITS,
} from "@/graphql/config";
import { TIMEZONES, CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import { useQuery } from "@vue/apollo-composable";
import { computed } from "vue";
@@ -47,7 +23,7 @@ export function useAnonymousParticipationConfig() {
loading,
} = useQuery<{
config: Pick<IConfig, "anonymous">;
}>(ANONYMOUS_PARTICIPATION_CONFIG);
}>(CONFIG);
const anonymousParticipationConfig = computed(
() => configResult.value?.config?.anonymous?.participation
@@ -63,7 +39,7 @@ export function useAnonymousReportsConfig() {
loading,
} = useQuery<{
config: Pick<IConfig, "anonymous">;
}>(ANONYMOUS_REPORTS_CONFIG);
}>(CONFIG);
const anonymousReportsConfig = computed(
() => configResult.value?.config?.anonymous?.reports
@@ -74,7 +50,7 @@ export function useAnonymousReportsConfig() {
export function useInstanceName() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "name">;
}>(ABOUT);
}>(CONFIG);
const instanceName = computed(() => result.value?.config?.name);
return { instanceName, error, loading };
@@ -83,7 +59,7 @@ export function useInstanceName() {
export function useInstanceLogoUrl() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "instanceLogo">;
}>(INSTANCE_LOGO);
}>(CONFIG);
const instanceLogoUrl = computed(
() => result.value?.config?.instanceLogo?.url
@@ -94,7 +70,7 @@ export function useInstanceLogoUrl() {
export function useColors() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "primaryColor" | "secondaryColor">;
}>(COLORS);
}>(CONFIG);
const primaryColor = computed(() => result.value?.config?.primaryColor);
const secondaryColor = computed(() => result.value?.config?.secondaryColor);
@@ -104,7 +80,7 @@ export function useColors() {
export function useDefaultPicture() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "defaultPicture">;
}>(DEFAULT_PICTURE);
}>(CONFIG);
const defaultPicture = computed(() => result.value?.config?.defaultPicture);
return { defaultPicture, error, loading };
@@ -113,7 +89,7 @@ export function useDefaultPicture() {
export function useAnonymousActorId() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "anonymous">;
}>(ANONYMOUS_ACTOR_ID);
}>(CONFIG);
const anonymousActorId = computed(
() => result.value?.config?.anonymous?.actorId
@@ -124,7 +100,7 @@ export function useAnonymousActorId() {
export function useUploadLimits() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "uploadLimits">;
}>(UPLOAD_LIMITS);
}>(CONFIG);
const uploadLimits = computed(() => result.value?.config?.uploadLimits);
return { uploadLimits, error, loading };
@@ -133,7 +109,7 @@ export function useUploadLimits() {
export function useEventCategories() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "eventCategories">;
}>(EVENT_CATEGORIES);
}>(CONFIG);
const eventCategories = computed(() => result.value?.config.eventCategories);
return { eventCategories, error, loading };
@@ -142,7 +118,7 @@ export function useEventCategories() {
export function useRestrictions() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "restrictions">;
}>(RESTRICTIONS);
}>(CONFIG);
const restrictions = computed(() => result.value?.config.restrictions);
return { restrictions, error, loading };
@@ -151,7 +127,7 @@ export function useRestrictions() {
export function useExportFormats() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "exportFormats">;
}>(EVENT_PARTICIPANTS);
}>(CONFIG);
const exportFormats = computed(() => result.value?.config?.exportFormats);
return { exportFormats, error, loading };
}
@@ -159,7 +135,7 @@ export function useExportFormats() {
export function useGeocodingAutocomplete() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "geocoding">;
}>(GEOCODING_AUTOCOMPLETE);
}>(CONFIG);
const geocodingAutocomplete = computed(
() => result.value?.config?.geocoding?.autocomplete
);
@@ -169,7 +145,7 @@ export function useGeocodingAutocomplete() {
export function useMapTiles() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "maps">;
}>(MAPS_TILES);
}>(CONFIG);
const tiles = computed(() => result.value?.config.maps.tiles);
return { tiles, error, loading };
@@ -178,7 +154,7 @@ export function useMapTiles() {
export function useRoutingType() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "maps">;
}>(ROUTING_TYPE);
}>(CONFIG);
const routingType = computed(() => result.value?.config.maps.routing.type);
return { routingType, error, loading };
@@ -187,7 +163,7 @@ export function useRoutingType() {
export function useFeatures() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "features">;
}>(FEATURES);
}>(CONFIG);
const features = computed(() => result.value?.config.features);
return { features, error, loading };
@@ -196,7 +172,7 @@ export function useFeatures() {
export function useResourceProviders() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "resourceProviders">;
}>(RESOURCE_PROVIDERS);
}>(CONFIG);
const resourceProviders = computed(
() => result.value?.config.resourceProviders
@@ -207,7 +183,7 @@ export function useResourceProviders() {
export function useServerProvidedLocation() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "location">;
}>(LOCATION);
}>(CONFIG);
const location = computed(() => result.value?.config.location);
return { location, error, loading };
@@ -216,7 +192,7 @@ export function useServerProvidedLocation() {
export function useIsDemoMode() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "demoMode">;
}>(DEMO_MODE);
}>(CONFIG);
const isDemoMode = computed(() => result.value?.config.demoMode);
return { isDemoMode, error, loading };
@@ -225,7 +201,7 @@ export function useIsDemoMode() {
export function useIsLongEvents() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "longEvents">;
}>(LONG_EVENTS);
}>(CONFIG);
const isLongEvents = computed(() => result.value?.config.longEvents);
return { isLongEvents, error, loading };
@@ -234,7 +210,7 @@ export function useIsLongEvents() {
export function useAnalytics() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "analytics">;
}>(ANALYTICS);
}>(CONFIG);
const analytics = computed(() => result.value?.config.analytics);
return { analytics, error, loading };
@@ -243,7 +219,7 @@ export function useAnalytics() {
export function useSearchConfig() {
const { result, error, loading, onResult } = useQuery<{
config: Pick<IConfig, "search">;
}>(SEARCH_CONFIG);
}>(CONFIG);
const searchConfig = computed(() => result.value?.config.search);
return { searchConfig, error, loading, onResult };
@@ -255,7 +231,7 @@ export function useRegistrationConfig() {
IConfig,
"registrationsOpen" | "registrationsAllowlist" | "auth"
>;
}>(REGISTRATIONS);
}>(CONFIG);
const registrationsOpen = computed(
() => result.value?.config?.registrationsOpen

View File

@@ -6,7 +6,10 @@ export const CONFIG = gql`
name
description
slogan
contact
languages
version
federating
registrationsOpen
registrationsAllowlist
demoMode
@@ -138,6 +141,10 @@ export const CONFIG = gql`
}
`;
// To avoid redundancy, this GraphQL query should ideally be
// split into two separate queries:
// - CONFIG
// - TIMEZONES
export const CONFIG_EDIT_EVENT = gql`
query EditEventConfig {
config {
@@ -167,6 +174,8 @@ export const CONFIG_EDIT_EVENT = gql`
}
`;
// TERMS details are not requested in FullConfig
// because they need the locale variable, so keep them in a separate request
export const TERMS = gql`
query Terms($locale: String) {
config {
@@ -179,6 +188,13 @@ export const TERMS = gql`
}
`;
// ABOUT details are not requested in FullConfig
// because longDescription can be heavy, so keep them in a separate request
//
// To avoid redundancy, this GraphQL query should ideally be
// split into two separate queries:
// - ABOUT with only longDescription request
// - CONFIG
export const ABOUT = gql`
query About {
config {
@@ -204,15 +220,8 @@ export const ABOUT = gql`
}
`;
export const CONTACT = gql`
query Contact {
config {
name
contact
}
}
`;
// RULES details are not requested in FullConfig
// because rules can be heavy, so keep them in a separate request
export const RULES = gql`
query Rules {
config {
@@ -221,6 +230,8 @@ export const RULES = gql`
}
`;
// PRIVACY details are not requested in FullConfig
// because they need the locale variable, so keep them in a separate request
export const PRIVACY = gql`
query Privacy($locale: String) {
config {
@@ -233,6 +244,8 @@ export const PRIVACY = gql`
}
`;
// TIMEZONES details are not requested in FullConfig
// because timezones are heavy, so keep them in a separate request
export const TIMEZONES = gql`
query Timezones {
config {
@@ -240,289 +253,3 @@ export const TIMEZONES = gql`
}
}
`;
export const WEB_PUSH = gql`
query WebPush {
config {
webPush {
enabled
publicKey
}
}
}
`;
export const EVENT_PARTICIPANTS = gql`
query EventParticipants {
config {
exportFormats {
eventParticipants
}
}
}
`;
export const ANONYMOUS_PARTICIPATION_CONFIG = gql`
query AnonymousParticipationConfig {
config {
anonymous {
participation {
allowed
validation {
email {
enabled
confirmationRequired
}
captcha {
enabled
}
}
}
}
}
}
`;
export const ANONYMOUS_REPORTS_CONFIG = gql`
query AnonymousParticipationConfig {
config {
anonymous {
reports {
allowed
}
}
}
}
`;
export const INSTANCE_NAME = gql`
query InstanceName {
config {
name
}
}
`;
export const ANONYMOUS_ACTOR_ID = gql`
query AnonymousActorId {
config {
anonymous {
actorId
}
}
}
`;
export const UPLOAD_LIMITS = gql`
query UploadLimits {
config {
uploadLimits {
default
avatar
banner
}
}
}
`;
export const EVENT_CATEGORIES = gql`
query EventCategories {
config {
eventCategories {
id
label
}
}
}
`;
export const RESTRICTIONS = gql`
query OnlyGroupsCanCreateEvents {
config {
restrictions {
onlyGroupsCanCreateEvents
onlyAdminCanCreateGroups
}
}
}
`;
export const GEOCODING_AUTOCOMPLETE = gql`
query GeoCodingAutocomplete {
config {
geocoding {
autocomplete
}
}
}
`;
export const MAPS_TILES = gql`
query MapsTiles {
config {
maps {
tiles {
endpoint
attribution
}
}
}
}
`;
export const ROUTING_TYPE = gql`
query RoutingType {
config {
maps {
routing {
type
}
}
}
}
`;
export const FEATURES = gql`
query Features {
config {
features {
groups
eventCreation
eventExternal
antispam
}
}
}
`;
export const RESOURCE_PROVIDERS = gql`
query ResourceProviders {
config {
resourceProviders {
type
endpoint
software
}
}
}
`;
export const LOGIN_CONFIG = gql`
query LoginConfig {
config {
auth {
databaseLogin
oauthProviders {
id
label
}
}
registrationsOpen
}
}
`;
export const LOCATION = gql`
query Location {
config {
location {
latitude
longitude
# accuracyRadius
}
}
}
`;
export const DEMO_MODE = gql`
query DemoMode {
config {
demoMode
}
}
`;
export const LONG_EVENTS = gql`
query LongEvents {
config {
longEvents
}
}
`;
export const ANALYTICS = gql`
query Analytics {
config {
analytics {
id
enabled
configuration {
key
value
type
}
}
}
}
`;
export const SEARCH_CONFIG = gql`
query SearchConfig {
config {
search {
global {
isEnabled
isDefault
}
}
}
}
`;
export const INSTANCE_LOGO = gql`
query InstanceLogo {
config {
instanceLogo {
url
}
}
}
`;
export const COLORS = gql`
query Colors {
config {
primaryColor
secondaryColor
}
}
`;
export const DEFAULT_PICTURE = gql`
query DefaultPicture {
config {
defaultPicture {
uuid
url
name
metadata {
width
height
blurhash
}
}
}
}
`;
export const REGISTRATIONS = gql`
query Registrations {
config {
registrationsOpen
registrationsAllowlist
auth {
databaseLogin
}
}
}
`;

View File

@@ -1,6 +1,6 @@
import { apolloClient } from "@/vue-apollo";
import { provideApolloClient, useQuery } from "@vue/apollo-composable";
import { WEB_PUSH } from "../graphql/config";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
function urlBase64ToUint8Array(base64String: string): Uint8Array {
@@ -18,7 +18,7 @@ function urlBase64ToUint8Array(base64String: string): Uint8Array {
export async function subscribeUserToPush(): Promise<PushSubscription | null> {
const { onResult } = provideApolloClient(apolloClient)(() =>
useQuery<{ config: IConfig }>(WEB_PUSH)
useQuery<{ config: IConfig }>(CONFIG)
);
return new Promise((resolve, reject) => {

View File

@@ -3,10 +3,10 @@ import { provideApolloClient, useQuery } from "@vue/apollo-composable";
import { useHead as unHead } from "@unhead/vue";
import { apolloClient } from "@/vue-apollo";
import { IConfig } from "@/types/config.model";
import { ABOUT } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
const { result } = provideApolloClient(apolloClient)(() =>
useQuery<{ config: Pick<IConfig, "name"> }>(ABOUT)
useQuery<{ config: Pick<IConfig, "name"> }>(CONFIG)
);
const instanceName = computed(() => result.value?.config?.name);

View File

@@ -106,7 +106,7 @@
</template>
<script lang="ts" setup>
import { ABOUT } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import RouteName from "../router/name";
import { useQuery } from "@vue/apollo-composable";
@@ -117,7 +117,7 @@ import { useHead } from "@/utils/head";
const { currentUser } = useCurrentUserClient();
const { result: configResult } = useQuery<{ config: IConfig }>(ABOUT);
const { result: configResult } = useQuery<{ config: IConfig }>(CONFIG);
const config = computed(() => configResult.value?.config);

View File

@@ -199,7 +199,7 @@ import {
import { useServerProvidedLocation } from "@/composition/apollo/config";
import QuickPublish from "@/components/Home/QuickPublish.vue";
import ShortSearch from "@/components/Home/ShortSearch.vue";
import { ABOUT } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import { useI18n } from "vue-i18n";
@@ -214,7 +214,7 @@ const { result: aboutConfigResult } = useQuery<{
IConfig,
"name" | "description" | "slogan" | "registrationsOpen"
>;
}>(ABOUT);
}>(CONFIG);
const config = computed(() => aboutConfigResult.value?.config);

View File

@@ -326,7 +326,7 @@ import {
UNREGISTER_PUSH_MUTATION,
} from "@/graphql/webPush";
import merge from "lodash/merge";
import { WEB_PUSH } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { useMutation, useQuery } from "@vue/apollo-composable";
import {
computed,
@@ -357,7 +357,7 @@ const feedTokens = computed(() =>
const { result: webPushEnabledResult } = useQuery<{
config: Pick<IConfig, "webPush">;
}>(WEB_PUSH);
}>(CONFIG);
const webPushEnabled = computed(
() => webPushEnabledResult.value?.config?.webPush.enabled

View File

@@ -126,7 +126,7 @@
<script setup lang="ts">
import { LOGIN } from "@/graphql/auth";
import { LOGIN_CONFIG } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { LOGGED_USER_LOCATION } from "@/graphql/user";
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
import { IConfig } from "@/types/config.model";
@@ -160,7 +160,7 @@ const configQuery = useQuery<{
IConfig,
"auth" | "registrationsOpen" | "registrationsAllowlist"
>;
}>(LOGIN_CONFIG);
}>(CONFIG);
const config = computed(() => configQuery.result.value?.config);

View File

@@ -11,7 +11,7 @@ import {
MockApolloClient,
RequestHandler,
} from "mock-apollo-client";
import { ANONYMOUS_ACTOR_ID } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { FETCH_EVENT_BASIC, JOIN_EVENT } from "@/graphql/event";
import { IEvent } from "@/types/event.model";
import { anonymousActorIdMock } from "../../mocks/config";
@@ -91,7 +91,7 @@ describe("ParticipationWithoutAccount", () => {
...handlers,
};
mockClient.setRequestHandler(
ANONYMOUS_ACTOR_ID,
CONFIG,
requestHandlers.anonymousActorIdQueryHandler
);
mockClient.setRequestHandler(

View File

@@ -4,7 +4,7 @@ import { beforeEach, describe, it, expect } from "vitest";
import { enUS } from "date-fns/locale";
import { routes } from "@/router";
import { createRouter, createWebHistory, Router } from "vue-router";
import { DEFAULT_PICTURE } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import { getMockClient } from "../../mocks/client";
let router: Router;
@@ -32,7 +32,7 @@ const generateWrapper = (
customPostData: Record<string, unknown> = {},
customProps: Record<string, unknown> = {}
) => {
const global_data = getMockClient([DEFAULT_PICTURE]);
const global_data = getMockClient([CONFIG]);
global_data.provide.dateFnsLocale = enUS;
global_data.plugins = [router];
return mount(PostListItem, {

View File

@@ -7,7 +7,7 @@ import {
} from "mock-apollo-client";
import buildCurrentUserResolver from "@/apollo/user";
import { loginMock as loginConfigMock } from "../../mocks/config";
import { LOGIN_CONFIG } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
import {
loginMock,
loginResponseMock,
@@ -72,10 +72,7 @@ describe("Render login form", () => {
mockhdl: vi.fn().mockResolvedValue(nullMock),
...handlers,
};
mockClient.setRequestHandler(
LOGIN_CONFIG,
requestHandlers.configQueryHandler
);
mockClient.setRequestHandler(CONFIG, requestHandlers.configQueryHandler);
mockClient.setRequestHandler(LOGIN, requestHandlers.loginMutationHandler);
mockClient.setRequestHandler(IDENTITIES, requestHandlers.identity);
mockClient.setRequestHandler(LOGGED_USER_LOCATION, requestHandlers.mockhdl);

View File

@@ -5,14 +5,14 @@ import {
createMockIntersectionObserver,
getMockClient,
} from "../../mocks/client";
import { DEFAULT_PICTURE } from "@/graphql/config";
import { CONFIG } from "@/graphql/config"; //
describe("Event Card Story", () => {
let wrapper: VueWrapper;
const generateWrapper = () => {
wrapper = mount(EventCardStory, {
global: getMockClient([DEFAULT_PICTURE]),
global: getMockClient([CONFIG]),
});
};
beforeEach(() => {

View File

@@ -5,14 +5,14 @@ import {
createMockIntersectionObserver,
getMockClient,
} from "../../mocks/client";
import { DEFAULT_PICTURE } from "@/graphql/config";
import { CONFIG } from "@/graphql/config";
describe("Post List Item Story", () => {
let wrapper: VueWrapper;
const generateWrapper = () => {
wrapper = mount(PostListItemStory, {
global: getMockClient([DEFAULT_PICTURE]),
global: getMockClient([CONFIG]),
});
};
beforeEach(() => {