From 28b1dea6ee05537862efde2a5c2c970b552308ae Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Tue, 31 Oct 2023 13:52:47 +0100 Subject: [PATCH] perf: Do not query themes and organizations separately --- app/browser/context.tsx | 34 +--------- app/browser/dataset-browse.spec.tsx | 24 ++----- app/browser/dataset-browse.tsx | 57 +++++----------- app/browser/filters.tsx | 29 ++------ app/browser/select-dataset-step.tsx | 48 ++++++++++++- app/graphql/queries/data-cubes.graphql | 39 ----------- app/graphql/query-hooks.ts | 94 -------------------------- app/graphql/resolver-types.ts | 28 -------- app/graphql/resolvers/index.ts | 12 ---- app/graphql/resolvers/rdf.ts | 34 ---------- app/graphql/resolvers/sql.ts | 13 ---- app/graphql/schema.graphql | 16 ----- 12 files changed, 76 insertions(+), 352 deletions(-) diff --git a/app/browser/context.tsx b/app/browser/context.tsx index 452f771c11..d09b991bff 100644 --- a/app/browser/context.tsx +++ b/app/browser/context.tsx @@ -7,14 +7,8 @@ import Link from "next/link"; import { Router, useRouter } from "next/router"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { - SearchCubeResultOrder, - useOrganizationsQuery, - useThemesQuery, -} from "@/graphql/query-hooks"; -import { useLocale } from "@/locales/use-locale"; +import { SearchCubeResultOrder } from "@/graphql/query-hooks"; import { BrowseParams } from "@/pages/browse"; -import { useDataSourceStore } from "@/stores/data-source"; import useEvent from "@/utils/use-event"; import { getFiltersFromParams } from "./filters"; @@ -114,10 +108,7 @@ const useQueryParamsState = ( }; export const useBrowseState = () => { - const { dataSource } = useDataSourceStore(); - const locale = useLocale(); const inputRef = useRef(null); - const [browseParams, setParams] = useQueryParamsState( {}, { @@ -125,7 +116,6 @@ export const useBrowseState = () => { serialize: buildURLFromBrowseState, } ); - const { search, type, @@ -135,29 +125,9 @@ export const useBrowseState = () => { dataset: paramDataset, } = browseParams; - const [{ data: themeData }] = useThemesQuery({ - variables: { - sourceType: dataSource.type, - sourceUrl: dataSource.url, - locale, - }, - pause: !!paramDataset, - }); - const [{ data: orgData }] = useOrganizationsQuery({ - variables: { - sourceType: dataSource.type, - sourceUrl: dataSource.url, - locale, - }, - pause: !!paramDataset, - }); - // Support /browse?dataset= and legacy /browse/dataset/ const dataset = type === "dataset" ? iri : paramDataset; - const filters = getFiltersFromParams(browseParams, { - themes: themeData?.themes, - organizations: orgData?.organizations, - }); + const filters = getFiltersFromParams(browseParams); const setSearch = useEvent((v: string) => setParams({ search: v })); const setIncludeDrafts = useEvent((v: boolean) => diff --git a/app/browser/dataset-browse.spec.tsx b/app/browser/dataset-browse.spec.tsx index bdd3548a61..842fb0f6e0 100644 --- a/app/browser/dataset-browse.spec.tsx +++ b/app/browser/dataset-browse.spec.tsx @@ -1,30 +1,14 @@ -import { DataCubeOrganization, DataCubeTheme } from "@/graphql/query-hooks"; import { BrowseParams } from "@/pages/browse"; import { getFiltersFromParams } from "./filters"; -const ctx = { - themes: [ - { - iri: "https://fake-iri-theme", - __typename: "DataCubeTheme", - }, - ] as DataCubeTheme[], - organizations: [ - { - iri: "https://fake-iri-organization", - __typename: "DataCubeOrganization", - }, - ] as DataCubeOrganization[], -}; - describe("getFiltersFromParams", () => { it("should work only for organization", () => { const params = { type: "organization", iri: "https://fake-iri-organization", } as BrowseParams; - const filters = getFiltersFromParams(params, ctx); + const filters = getFiltersFromParams(params); expect(filters).toEqual([ { __typename: "DataCubeOrganization", @@ -40,7 +24,7 @@ describe("getFiltersFromParams", () => { subtype: "organization", subiri: "https://fake-iri-organization", } as BrowseParams; - const filters = getFiltersFromParams(params, ctx); + const filters = getFiltersFromParams(params); expect(filters).toEqual([ { iri: "https://fake-iri-theme", __typename: "DataCubeTheme" }, { @@ -57,7 +41,7 @@ describe("getFiltersFromParams", () => { subtype: "theme", subiri: "https://fake-iri-theme", } as BrowseParams; - const filters = getFiltersFromParams(params, ctx); + const filters = getFiltersFromParams(params); expect(filters).toEqual([ { iri: "https://fake-iri-organization", @@ -72,7 +56,7 @@ describe("getFiltersFromParams", () => { type: "dataset", iri: "https://fake-iri-dataset", } as BrowseParams; - const filters = getFiltersFromParams(params, ctx); + const filters = getFiltersFromParams(params); expect(filters).toEqual([]); }); }); diff --git a/app/browser/dataset-browse.tsx b/app/browser/dataset-browse.tsx index 6d25f1f4b2..1c22bd969a 100644 --- a/app/browser/dataset-browse.tsx +++ b/app/browser/dataset-browse.tsx @@ -39,8 +39,6 @@ import { DataCubeTheme, SearchCubeResultOrder, SearchCubesQuery, - useOrganizationsQuery, - useThemesQuery, } from "@/graphql/query-hooks"; import { DataCubePublicationStatus, @@ -49,8 +47,6 @@ import { import SvgIcCategories from "@/icons/components/IcCategories"; import SvgIcClose from "@/icons/components/IcClose"; import SvgIcOrganisations from "@/icons/components/IcOrganisations"; -import { useLocale } from "@/locales/use-locale"; -import { useDataSourceStore } from "@/stores/data-source"; import isAttrEqual from "@/utils/is-attr-equal"; import useEvent from "@/utils/use-event"; @@ -525,6 +521,7 @@ const NavSection = ({ ); }, [counts, items]); const { isOpen, open, close } = useDisclosure(); + return (
@@ -545,7 +542,7 @@ const NavSection = ({ return ( { - const { dataSource } = useDataSourceStore(); - const locale = useLocale(); - const { filters, dataset } = useBrowseContext(); - const [{ data: allThemes }] = useThemesQuery({ - variables: { - sourceType: dataSource.type, - sourceUrl: dataSource.url, - locale, - }, - pause: !!dataset, - }); - const [{ data: allOrgs }] = useOrganizationsQuery({ - variables: { - sourceType: dataSource.type, - sourceUrl: dataSource.url, - locale, - }, - pause: !!dataset, - }); - +export const SearchFilters = ({ + cubes, + themes, + orgs, +}: { + cubes: SearchCubeResult[]; + themes: DataCubeTheme[]; + orgs: DataCubeOrganization[]; +}) => { + const { filters } = useBrowseContext(); const counts = useMemo(() => { const result: Record = {}; @@ -639,14 +625,7 @@ export const SearchFilters = ({ cubes }: { cubes: SearchCubeResult[] }) => { isAttrEqual("__typename", "DataCubeOrganization") ); - const [allThemesAlpha, allOrgsAlpha] = useMemo(() => { - return [ - allThemes ? sortBy(allThemes.themes, (x) => x?.label) : null, - allOrgs ? sortBy(allOrgs.organizations, (x) => x?.label) : null, - ]; - }, [allThemes, allOrgs]); - - const displayedThemes = allThemesAlpha?.filter((theme) => { + const displayedThemes = themes.filter((theme) => { if (!theme.label) { return false; } @@ -655,23 +634,23 @@ export const SearchFilters = ({ cubes }: { cubes: SearchCubeResult[] }) => { return false; } - if (themeFilter && themeFilter !== theme) { + if (themeFilter && themeFilter.iri !== theme.iri) { return false; } return true; }); - const displayedOrgs = allOrgsAlpha?.filter((org) => { + const displayedOrgs = orgs.filter((org) => { if (!org.label) { return false; } - if (!counts[org.iri] && orgFilter !== org) { + if (!counts[org.iri] && orgFilter?.iri !== org.iri) { return false; } - if (orgFilter && orgFilter !== org) { + if (orgFilter && orgFilter.iri !== org.iri) { return false; } @@ -721,7 +700,7 @@ export const SearchFilters = ({ cubes }: { cubes: SearchCubeResult[] }) => { icon={} label={Organizations} extra={ - orgFilter && filters.includes(orgFilter) ? ( + orgFilter && filters.map((d) => d.iri).includes(orgFilter.iri) ? ( { +export const getFiltersFromParams = (params: BrowseParams) => { const filters: BrowseFilter[] = []; const { type, subtype, iri, subiri, topic } = params; for (const [t, i] of [ @@ -28,17 +17,13 @@ export const getFiltersFromParams = ( [subtype, subiri], ]) { if (t && i && (t === "theme" || t === "organization")) { - const container = context[ - t === "theme" ? "themes" : "organizations" - ] as BrowseFilter[]; - const obj = container?.find((f) => i === f.iri); - if (obj) { - filters.push(obj); - } else { - break; - } + filters.push({ + __typename: t === "theme" ? "DataCubeTheme" : "DataCubeOrganization", + iri: i, + }); } } + if (topic) { filters.push({ __typename: "DataCubeAbout", diff --git a/app/browser/select-dataset-step.tsx b/app/browser/select-dataset-step.tsx index 85e18ae6db..48ef38cdb4 100644 --- a/app/browser/select-dataset-step.tsx +++ b/app/browser/select-dataset-step.tsx @@ -2,6 +2,8 @@ import { t, Trans } from "@lingui/macro"; import { Box, Button, Theme, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; import { AnimatePresence } from "framer-motion"; +import sortBy from "lodash/sortBy"; +import uniqBy from "lodash/uniqBy"; import Head from "next/head"; import NextLink from "next/link"; import { Router, useRouter } from "next/router"; @@ -32,7 +34,12 @@ import { PanelLeftWrapper, PanelMiddleWrapper, } from "@/configurator/components/layout"; -import { useSearchCubesQuery } from "@/graphql/query-hooks"; +import { truthy } from "@/domain/types"; +import { + DataCubeOrganization, + DataCubeTheme, + useSearchCubesQuery, +} from "@/graphql/query-hooks"; import { Icon } from "@/icons"; import { useConfiguratorState, useLocale } from "@/src"; @@ -206,6 +213,32 @@ const SelectDatasetStepContent = () => { }; }, [data, filters]); + const themes: DataCubeTheme[] = React.useMemo(() => { + return sortBy( + uniqBy( + cubes + .flatMap((d) => d.cube.themes) + .map((d) => ({ ...d, __typename: "DataCubeTheme" })), + (d) => d.iri + ), + (d) => d.label + ); + }, [cubes]); + + const orgs: DataCubeOrganization[] = React.useMemo(() => { + return sortBy( + uniqBy( + cubes + .map((d) => d.cube.creator) + .filter((d) => d?.iri) + .filter(truthy) + .map((d) => ({ ...d, __typename: "DataCubeOrganization" })), + (d) => d.iri + ), + (d) => d.label + ); + }, [cubes]); + if (configState.state !== "SELECTING_DATASET") { return null; } @@ -280,7 +313,7 @@ const SelectDatasetStepContent = () => { ) : ( - + )} @@ -329,7 +362,16 @@ const SelectDatasetStepContent = () => { className={classes.filters} variant="h1" > - {queryFilters.map((d) => d.label).join(", ")} + {queryFilters + .map((d) => { + const searchList = + d.type === "DataCubeTheme" ? themes : orgs; + const item = searchList.find( + ({ iri }) => iri === d.value + ); + return (item ?? d).label; + }) + .join(", ")} )} diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index c8dadf8894..ccd4b9411d 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -388,45 +388,6 @@ query PossibleFilters( } } -query Themes($sourceType: String!, $sourceUrl: String!, $locale: String!) { - themes(sourceType: $sourceType, sourceUrl: $sourceUrl, locale: $locale) { - iri - label - } -} - -query Organizations( - $sourceType: String! - $sourceUrl: String! - $locale: String! -) { - organizations( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - ) { - iri - label - } -} - -query Subthemes( - $sourceType: String! - $sourceUrl: String! - $locale: String! - $parentIri: String! -) { - subthemes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - parentIri: $parentIri - ) { - label - iri - } -} - fragment hierarchyValueFields on HierarchyValue { value dimensionIri diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index 12c90b434f..f2e65e15f5 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -368,9 +368,6 @@ export type Query = { dataCubeByIri?: Maybe; possibleFilters: Array; searchCubes: Array; - themes: Array; - subthemes: Array; - organizations: Array; }; @@ -405,28 +402,6 @@ export type QuerySearchCubesArgs = { }; -export type QueryThemesArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}; - - -export type QuerySubthemesArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - parentIri: Scalars['String']; -}; - - -export type QueryOrganizationsArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}; - - export type RelatedDimension = { __typename: 'RelatedDimension'; type: Scalars['String']; @@ -1065,34 +1040,6 @@ export type PossibleFiltersQueryVariables = Exact<{ export type PossibleFiltersQuery = { __typename: 'Query', possibleFilters: Array<{ __typename: 'ObservationFilter', iri: string, type: string, value?: Maybe }> }; -export type ThemesQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}>; - - -export type ThemesQuery = { __typename: 'Query', themes: Array<{ __typename: 'DataCubeTheme', iri: string, label?: Maybe }> }; - -export type OrganizationsQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}>; - - -export type OrganizationsQuery = { __typename: 'Query', organizations: Array<{ __typename: 'DataCubeOrganization', iri: string, label?: Maybe }> }; - -export type SubthemesQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - parentIri: Scalars['String']; -}>; - - -export type SubthemesQuery = { __typename: 'Query', subthemes: Array<{ __typename: 'DataCubeTheme', label?: Maybe, iri: string }> }; - export type HierarchyValueFieldsFragment = { __typename: 'HierarchyValue', value: string, dimensionIri: string, depth: number, label: string, alternateName?: Maybe, hasValue?: Maybe, position?: Maybe, identifier?: Maybe }; export type DimensionHierarchyQueryVariables = Exact<{ @@ -1513,47 +1460,6 @@ export const PossibleFiltersDocument = gql` export function usePossibleFiltersQuery(options: Omit, 'query'> = {}) { return Urql.useQuery({ query: PossibleFiltersDocument, ...options }); }; -export const ThemesDocument = gql` - query Themes($sourceType: String!, $sourceUrl: String!, $locale: String!) { - themes(sourceType: $sourceType, sourceUrl: $sourceUrl, locale: $locale) { - iri - label - } -} - `; - -export function useThemesQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: ThemesDocument, ...options }); -}; -export const OrganizationsDocument = gql` - query Organizations($sourceType: String!, $sourceUrl: String!, $locale: String!) { - organizations(sourceType: $sourceType, sourceUrl: $sourceUrl, locale: $locale) { - iri - label - } -} - `; - -export function useOrganizationsQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: OrganizationsDocument, ...options }); -}; -export const SubthemesDocument = gql` - query Subthemes($sourceType: String!, $sourceUrl: String!, $locale: String!, $parentIri: String!) { - subthemes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - parentIri: $parentIri - ) { - label - iri - } -} - `; - -export function useSubthemesQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: SubthemesDocument, ...options }); -}; export const DimensionHierarchyDocument = gql` query DimensionHierarchy($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeIri: String!, $dimensionIri: String!) { dataCubeByIri( diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index 27d5afc857..25dfe7f0ee 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -369,9 +369,6 @@ export type Query = { dataCubeByIri?: Maybe; possibleFilters: Array; searchCubes: Array; - themes: Array; - subthemes: Array; - organizations: Array; }; @@ -406,28 +403,6 @@ export type QuerySearchCubesArgs = { }; -export type QueryThemesArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}; - - -export type QuerySubthemesArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - parentIri: Scalars['String']; -}; - - -export type QueryOrganizationsArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}; - - export type RelatedDimension = { __typename?: 'RelatedDimension'; type: Scalars['String']; @@ -930,9 +905,6 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; possibleFilters?: Resolver, ParentType, ContextType, RequireFields>; searchCubes?: Resolver, ParentType, ContextType, RequireFields>; - themes?: Resolver, ParentType, ContextType, RequireFields>; - subthemes?: Resolver, ParentType, ContextType, RequireFields>; - organizations?: Resolver, ParentType, ContextType, RequireFields>; }>; export interface RawObservationScalarConfig extends GraphQLScalarTypeConfig { diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index 179345e39a..e580022c79 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -31,18 +31,6 @@ export const Query: QueryResolvers = { const source = getSource(args.sourceType); return await source.possibleFilters(parent, args, context, info); }, - themes: async (parent, args, context, info) => { - const source = getSource(args.sourceType); - return await source.themes(parent, args, context, info); - }, - subthemes: async (parent, args, context, info) => { - const source = getSource(args.sourceType); - return await source.subthemes(parent, args, context, info); - }, - organizations: async (parent, args, context, info) => { - const source = getSource(args.sourceType); - return await source.organizations(parent, args, context, info); - }, }; const DataCube: DataCubeResolvers = { diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts index 53b3b73a21..709fb76dc0 100644 --- a/app/graphql/resolvers/rdf.ts +++ b/app/graphql/resolvers/rdf.ts @@ -5,7 +5,6 @@ import { LRUCache } from "typescript-lru-cache"; import { Filters } from "@/configurator"; import { DimensionValue } from "@/domain/data"; -import { truthy } from "@/domain/types"; import { Loaders } from "@/graphql/context"; import { DataCubeResolvers, @@ -20,11 +19,6 @@ import { getCubeObservations, getResolvedCube, } from "@/rdf/queries"; -import { - loadOrganizations, - loadSubthemes, - loadThemes, -} from "@/rdf/query-cube-metadata"; import { unversionObservation } from "@/rdf/query-dimension-values"; import { queryHierarchy } from "@/rdf/query-hierarchies"; import { SearchResult, searchCubes as _searchCubes } from "@/rdf/query-search"; @@ -135,34 +129,6 @@ export const possibleFilters: NonNullable = return []; }; -export const themes: NonNullable = async ( - _, - { locale }, - { setup }, - info -) => { - const { sparqlClient } = await setup(info); - return (await loadThemes({ locale, sparqlClient })).filter(truthy); -}; - -export const subthemes: NonNullable = async ( - _, - { locale, parentIri }, - { setup }, - info -) => { - const { sparqlClient } = await setup(info); - return (await loadSubthemes({ locale, parentIri, sparqlClient })).filter( - truthy - ); -}; - -export const organizations: NonNullable = - async (_, { locale }, { setup }, info) => { - const { sparqlClient } = await setup(info); - return (await loadOrganizations({ locale, sparqlClient })).filter(truthy); - }; - export const dataCubeDimensions: NonNullable = async ({ cube, locale }, { componentIris }, { setup }, info) => { const { sparqlClient, cache } = await setup(info); diff --git a/app/graphql/resolvers/sql.ts b/app/graphql/resolvers/sql.ts index 2b9c34e1bb..bd22e2fab6 100644 --- a/app/graphql/resolvers/sql.ts +++ b/app/graphql/resolvers/sql.ts @@ -163,19 +163,6 @@ export const possibleFilters: NonNullable = return []; }; -export const themes: NonNullable = async () => { - return []; -}; - -export const subthemes: NonNullable = async () => { - return []; -}; - -export const organizations: NonNullable = - async () => { - return []; - }; - export const dataCubeDimensions: NonNullable = async ({ cube }) => { // FIXME: type of cube should be different for RDF and SQL. diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 04cdc66e3c..55def3c199 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -382,20 +382,4 @@ type Query { includeDrafts: Boolean filters: [SearchCubeFilter!] ): [SearchCubeResult!]! - themes( - sourceType: String! - sourceUrl: String! - locale: String! - ): [DataCubeTheme!]! - subthemes( - sourceType: String! - sourceUrl: String! - locale: String! - parentIri: String! - ): [DataCubeTheme!]! - organizations( - sourceType: String! - sourceUrl: String! - locale: String! - ): [DataCubeOrganization!]! }