diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx index 75ed266fc..b350fb964 100644 --- a/.storybook/decorators.tsx +++ b/.storybook/decorators.tsx @@ -9,8 +9,11 @@ import { i18n } from "../app/locales/locales"; import { theme } from "../app/themes/federal"; import AsyncLocalizationProvider from "../app/utils/l10n-provider"; +import { Decorator } from "@storybook/react"; +import { Client, Provider } from "urql"; + export const AppContextDecorator = (Story: NextPage) => ( - + @@ -36,3 +39,17 @@ export const RouterDecorator = (Story: NextPage) => { ); }; + +const graphqlURL = + process.env.NODE_ENV === "production" + ? "/api/graphql" + : "http://localhost:3000/api/graphql"; + +export const UrqlDecorator: Decorator = (Story) => { + const client = new Client({ url: graphqlURL }); + return ( + + + + ); +}; diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 837375a62..0436210b4 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,6 +1,10 @@ import type { Preview } from "@storybook/react"; import type { IndexEntry } from "@storybook/types"; -import { AppContextDecorator, RouterDecorator } from "./decorators"; +import { + AppContextDecorator, + RouterDecorator, + UrqlDecorator, +} from "./decorators"; const preview: Preview = { // @ts-ignore @@ -9,7 +13,7 @@ const preview: Preview = { basePath: "/profile", }, }, - decorators: [AppContextDecorator, RouterDecorator], + decorators: [AppContextDecorator, RouterDecorator, UrqlDecorator], options: { storySort: (a: IndexEntry, b: IndexEntry) => a.id === b.id diff --git a/app/components/graphql-search.stories.tsx b/app/components/graphql-search.stories.tsx new file mode 100644 index 000000000..a2649e300 --- /dev/null +++ b/app/components/graphql-search.stories.tsx @@ -0,0 +1,232 @@ +import { + Box, + Checkbox, + CircularProgress, + FormControlLabel, + ListItemText, + MenuItem, + OutlinedInput, + Select, + SelectChangeEvent, + Stack, + TextField, +} from "@mui/material"; +import { Meta } from "@storybook/react"; +import keyBy from "lodash/keyBy"; +import { useEffect, useState } from "react"; +import { ObjectInspector } from "react-inspector"; + +import { DatasetResult } from "@/browser/dataset-browse"; +import { Error } from "@/components/hint"; +import Tag from "@/components/tag"; +import { Termset } from "@/domain/data"; +import { truthy } from "@/domain/types"; +import { + SearchCubeFilterType, + useDataCubeTermsetsQuery, + useSearchCubesQuery, +} from "@/graphql/query-hooks"; + +export const Search = () => { + const [inputValue, setInputValue] = useState(""); + const [query, setQuery] = useState(""); + const [cube, setCube] = useState( + "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9" + ); + const [temporalDimension, setTemporalDimension] = useState("-"); + const [sharedDimensions, setSharedDimensions] = useState< + Termset["iri"][] | undefined + >(undefined); + + const [cubeTermsetsResults] = useDataCubeTermsetsQuery({ + variables: { + locale: "en", + sourceType: "sparql", + sourceUrl: "https://lindas.admin.ch/query", + cubeFilter: { + iri: cube, + }, + }, + }); + + const [searchCubesResult] = useSearchCubesQuery({ + pause: !sharedDimensions || sharedDimensions.length === 0, + variables: { + locale: "en", + sourceType: "sparql", + sourceUrl: "https://lindas.admin.ch/query", + filters: [ + sharedDimensions + ? { + type: SearchCubeFilterType.SharedDimensions, + value: sharedDimensions?.join(";"), + } + : null, + temporalDimension !== "-" + ? { + type: SearchCubeFilterType.TemporalDimension, + value: temporalDimension, + } + : null, + ].filter(truthy), + includeDrafts: false, + query, + }, + }); + + const handleChangeSharedDimensions = ( + ev: SelectChangeEvent + ) => { + const { + target: { value }, + } = ev; + setSharedDimensions( + // On autofill we get a stringified value. + typeof value === "string" ? value.split(",") : value + ); + }; + + useEffect(() => { + if ( + sharedDimensions === undefined && + cubeTermsetsResults?.data?.dataCubeTermsets + ) { + setSharedDimensions( + cubeTermsetsResults?.data?.dataCubeTermsets.map((sd) => sd.iri) + ); + } + }, [cubeTermsetsResults, sharedDimensions]); + + const cubeSharedDimensionsByIri = keyBy( + cubeTermsetsResults.data?.dataCubeTermsets ?? [], + (x) => x.iri + ); + + return ( +
+

Search results

+ + { + const value = (ev.target as HTMLInputElement).value; + setInputValue(value); + }} + onKeyUp={(ev) => { + if (ev.key === "Enter") { + setQuery(inputValue); + } + }} + /> + } + /> + + setCube(ev.target.value)} + value={cube} + > + + + + + } + /> + + + + {cubeTermsetsResults.fetching ? ( + + ) : null} + + } + /> + + setTemporalDimension(ev.target.value as string)} + > + - + Year + Month + + } + /> + + + + + + {searchCubesResult.fetching ? : null} + {searchCubesResult.error ? ( + {searchCubesResult.error.message} + ) : null} + + {searchCubesResult?.data?.searchCubes.map((item) => ( + + ))} +
+ ); +}; + +export default { + title: "Data / Search", +} as Meta; diff --git a/app/components/graphql-termsets.stories.tsx b/app/components/graphql-termsets.stories.tsx new file mode 100644 index 000000000..bf73c5ef2 --- /dev/null +++ b/app/components/graphql-termsets.stories.tsx @@ -0,0 +1,78 @@ +import { + CircularProgress, + FormControlLabel, + Select, + Stack, +} from "@mui/material"; +import { Meta } from "@storybook/react"; +import { useState } from "react"; + +import Tag from "@/components/tag"; +import { useDataCubeTermsetsQuery } from "@/graphql/query-hooks"; + +export const Termsets = () => { + const [cube, setCube] = useState( + "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9" + ); + const [result] = useDataCubeTermsetsQuery({ + variables: { + locale: "en", + sourceType: "sparql", + sourceUrl: "https://lindas.admin.ch/query", + cubeFilter: { + iri: cube, + latest: true, + }, + }, + }); + + const { data } = result; + + return ( +
+

Termsets

+ + + + {result.fetching ? : null} + + } + /> + + + {result.error ?
Error: {result.error.message}
: null} + + + {data?.dataCubeTermsets?.map((termset) => ( + + {termset.label} + + ))} + +
+ ); +}; + +export default { + title: "Data / Termsets", +} as Meta; diff --git a/app/components/graphql.stories.tsx b/app/components/graphql.stories.tsx deleted file mode 100644 index d1f95c06f..000000000 --- a/app/components/graphql.stories.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { - Box, - FormControlLabel, - MenuItem, - Select, - Stack, - TextField, -} from "@mui/material"; -import { Meta } from "@storybook/react"; -import { useState } from "react"; -import { ObjectInspector } from "react-inspector"; -import { Client, Provider } from "urql"; - -import { DatasetResult } from "@/browser/dataset-browse"; -import { truthy } from "@/domain/types"; -import { - SearchCubeFilterType, - useSearchCubesQuery, -} from "@/graphql/query-hooks"; - -export const SharedDimensions = () => { - const [inputValue, setInputValue] = useState(""); - const [query, setQuery] = useState(""); - const [cube, setCube] = useState( - "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9" - ); - const [temporalDimension, setTemporalDimension] = useState("-"); - const [result] = useSearchCubesQuery({ - variables: { - locale: "en", - sourceType: "sparql", - sourceUrl: "https://lindas.admin.ch/query", - filters: [ - { - type: SearchCubeFilterType.SharedDimension, - value: cube, - }, - temporalDimension !== "-" - ? { - type: SearchCubeFilterType.TemporalDimension, - value: temporalDimension, - } - : null, - ].filter(truthy), - includeDrafts: false, - query, - }, - }); - - if (result.fetching) { - return
Loading...
; - } - - if (result.error) { - return
Error: {result.error.message}
; - } - - const { data } = result; - - return ( -
-

Search results

- - { - const value = (ev.target as HTMLInputElement).value; - setInputValue(value); - }} - onKeyUp={(ev) => { - if (ev.key === "Enter") { - setQuery(inputValue); - } - }} - /> - } - /> - - setCube(ev.target.value)} - value={cube} - > - - - - - } - /> - - setTemporalDimension(ev.target.value as string)} - > - - - Year - Month - - } - /> - - - - - - - {data?.searchCubes.map((item) => ( - - ))} -
- ); -}; - -const graphqlURL = - process.env.NODE_ENV === "production" - ? "/api/graphql" - : "http://localhost:3000/api/graphql"; - -// Define your Storybook story -export default { - title: "Data / Search", - decorators: [ - (Story) => { - const client = new Client({ url: graphqlURL }); - return ( - - - - ); - }, - ], -} as Meta; diff --git a/app/docs/dataset-result.mock.ts b/app/docs/dataset-result.mock.ts index d933a67a6..fcb309da4 100644 --- a/app/docs/dataset-result.mock.ts +++ b/app/docs/dataset-result.mock.ts @@ -1,6 +1,7 @@ +import { PartialSearchCube } from "@/browser/dataset-browse"; import { DataCubePublicationStatus } from "@/graphql/query-hooks"; -export const waldDatacubeResult = { +export const waldDatacubeResult: PartialSearchCube = { iri: "http://example.com/iri", creator: { iri: "http://example.com/iri", @@ -17,4 +18,5 @@ export const waldDatacubeResult = { description: "Comptes des exploitations forestières en francs, dès 2015", datePublished: "2020-10-10", publicationStatus: DataCubePublicationStatus.Published, + termsets: [], }; diff --git a/app/domain/data.ts b/app/domain/data.ts index b5a783c8f..4809a6015 100644 --- a/app/domain/data.ts +++ b/app/domain/data.ts @@ -94,6 +94,11 @@ export type DataCubePreview = { observations: Observation[]; }; +export type Termset = { + iri: string; + label: string; +}; + type ComponentRenderingConfig = { enableAnimation: boolean; enableCustomSort: boolean; diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index 1e1a3eeef..2da12219e 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -26,6 +26,20 @@ query DataCubeMetadata( ) } +query DataCubeTermsets( + $sourceType: String! + $sourceUrl: String! + $locale: String! + $cubeFilter: DataCubeTermsetFilter! +) { + dataCubeTermsets( + cubeFilter: $cubeFilter + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + ) +} + query DataCubeObservations( $sourceType: String! $sourceUrl: String! diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index 664f43767..8e78de886 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -10,6 +10,7 @@ import { Observation } from '../domain/data'; import { RawObservation } from '../domain/data'; import { SearchCube } from '../domain/data'; import { SingleFilters } from '../configurator'; +import { Termset } from '../domain/data'; import gql from 'graphql-tag'; import * as Urql from 'urql'; export type Maybe = T | null; @@ -37,6 +38,7 @@ export type Scalars = { RawObservation: RawObservation; SearchCube: SearchCube; SingleFilters: SingleFilters; + Termset: Termset; ValueIdentifier: any; ValuePosition: any; }; @@ -129,6 +131,11 @@ export enum DataCubePublicationStatus { Published = 'PUBLISHED' } +export type DataCubeTermsetFilter = { + iri: Scalars['String']; + latest?: Maybe; +}; + export type DataCubeTheme = { __typename: 'DataCubeTheme'; iri: Scalars['String']; @@ -323,6 +330,7 @@ export type OrdinalMeasureValuesArgs = { export type Query = { __typename: 'Query'; dataCubeComponents: Scalars['DataCubeComponents']; + dataCubeTermsets: Array; dataCubeMetadata: Scalars['DataCubeMetadata']; dataCubeObservations: Scalars['DataCubeObservations']; dataCubePreview: Scalars['DataCubePreview']; @@ -340,6 +348,14 @@ export type QueryDataCubeComponentsArgs = { }; +export type QueryDataCubeTermsetsArgs = { + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; + locale: Scalars['String']; + cubeFilter: DataCubeTermsetFilter; +}; + + export type QueryDataCubeMetadataArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -417,7 +433,7 @@ export enum SearchCubeFilterType { DataCubeTheme = 'DataCubeTheme', DataCubeOrganization = 'DataCubeOrganization', DataCubeAbout = 'DataCubeAbout', - SharedDimension = 'SharedDimension' + SharedDimensions = 'SharedDimensions' } export type SearchCubeResult = { @@ -531,6 +547,7 @@ export type TemporalOrdinalDimensionValuesArgs = { disableLoad?: Maybe; }; + export enum TimeUnit { Year = 'Year', Month = 'Month', @@ -563,6 +580,16 @@ export type DataCubeMetadataQueryVariables = Exact<{ export type DataCubeMetadataQuery = { __typename: 'Query', dataCubeMetadata: DataCubeMetadata }; +export type DataCubeTermsetsQueryVariables = Exact<{ + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; + locale: Scalars['String']; + cubeFilter: DataCubeTermsetFilter; +}>; + + +export type DataCubeTermsetsQuery = { __typename: 'Query', dataCubeTermsets: Array }; + export type DataCubeObservationsQueryVariables = Exact<{ sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -646,6 +673,20 @@ export const DataCubeMetadataDocument = gql` export function useDataCubeMetadataQuery(options: Omit, 'query'> = {}) { return Urql.useQuery({ query: DataCubeMetadataDocument, ...options }); }; +export const DataCubeTermsetsDocument = gql` + query DataCubeTermsets($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubeTermsetFilter!) { + dataCubeTermsets( + cubeFilter: $cubeFilter + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + ) +} + `; + +export function useDataCubeTermsetsQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeTermsetsDocument, ...options }); +}; export const DataCubeObservationsDocument = gql` query DataCubeObservations($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubeObservationFilter!) { dataCubeObservations( diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index f80f5d577..4a27071a6 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -38,6 +38,7 @@ export type Scalars = { RawObservation: RawObservation; SearchCube: SearchCube; SingleFilters: SingleFilters; + Termset: any; ValueIdentifier: any; ValuePosition: any; }; @@ -130,6 +131,11 @@ export enum DataCubePublicationStatus { Published = 'PUBLISHED' } +export type DataCubeTermsetFilter = { + iri: Scalars['String']; + latest?: Maybe; +}; + export type DataCubeTheme = { __typename?: 'DataCubeTheme'; iri: Scalars['String']; @@ -324,6 +330,7 @@ export type OrdinalMeasureValuesArgs = { export type Query = { __typename?: 'Query'; dataCubeComponents: Scalars['DataCubeComponents']; + dataCubeTermsets: Array; dataCubeMetadata: Scalars['DataCubeMetadata']; dataCubeObservations: Scalars['DataCubeObservations']; dataCubePreview: Scalars['DataCubePreview']; @@ -341,6 +348,14 @@ export type QueryDataCubeComponentsArgs = { }; +export type QueryDataCubeTermsetsArgs = { + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; + locale: Scalars['String']; + cubeFilter: DataCubeTermsetFilter; +}; + + export type QueryDataCubeMetadataArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -418,7 +433,7 @@ export enum SearchCubeFilterType { DataCubeTheme = 'DataCubeTheme', DataCubeOrganization = 'DataCubeOrganization', DataCubeAbout = 'DataCubeAbout', - SharedDimension = 'SharedDimension' + SharedDimensions = 'SharedDimensions' } export type SearchCubeResult = { @@ -532,6 +547,7 @@ export type TemporalOrdinalDimensionValuesArgs = { disableLoad?: Maybe; }; + export enum TimeUnit { Year = 'Year', Month = 'Month', @@ -623,6 +639,7 @@ export type ResolversTypes = ResolversObject<{ DataCubePreview: ResolverTypeWrapper; DataCubePreviewFilter: DataCubePreviewFilter; DataCubePublicationStatus: DataCubePublicationStatus; + DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: ResolverTypeWrapper; Dimension: ResolverTypeWrapper; Int: ResolverTypeWrapper; @@ -656,6 +673,7 @@ export type ResolversTypes = ResolversObject<{ TemporalDimension: ResolverTypeWrapper; TemporalEntityDimension: ResolverTypeWrapper; TemporalOrdinalDimension: ResolverTypeWrapper; + Termset: ResolverTypeWrapper; TimeUnit: TimeUnit; ValueIdentifier: ResolverTypeWrapper; ValuePosition: ResolverTypeWrapper; @@ -675,6 +693,7 @@ export type ResolversParentTypes = ResolversObject<{ DataCubeOrganization: DataCubeOrganization; DataCubePreview: Scalars['DataCubePreview']; DataCubePreviewFilter: DataCubePreviewFilter; + DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: DataCubeTheme; Dimension: ResolvedDimension; Int: Scalars['Int']; @@ -705,6 +724,7 @@ export type ResolversParentTypes = ResolversObject<{ TemporalDimension: ResolvedDimension; TemporalEntityDimension: TemporalEntityDimension; TemporalOrdinalDimension: ResolvedDimension; + Termset: Scalars['Termset']; ValueIdentifier: Scalars['ValueIdentifier']; ValuePosition: Scalars['ValuePosition']; }>; @@ -912,6 +932,7 @@ export type OrdinalMeasureResolvers = ResolversObject<{ dataCubeComponents?: Resolver>; + dataCubeTermsets?: Resolver, ParentType, ContextType, RequireFields>; dataCubeMetadata?: Resolver>; dataCubeObservations?: Resolver>; dataCubePreview?: Resolver>; @@ -1010,6 +1031,10 @@ export type TemporalOrdinalDimensionResolvers; }>; +export interface TermsetScalarConfig extends GraphQLScalarTypeConfig { + name: 'Termset'; +} + export interface ValueIdentifierScalarConfig extends GraphQLScalarTypeConfig { name: 'ValueIdentifier'; } @@ -1052,6 +1077,7 @@ export type Resolvers = ResolversObject<{ TemporalDimension?: TemporalDimensionResolvers; TemporalEntityDimension?: TemporalEntityDimensionResolvers; TemporalOrdinalDimension?: TemporalOrdinalDimensionResolvers; + Termset?: GraphQLScalarType; ValueIdentifier?: GraphQLScalarType; ValuePosition?: GraphQLScalarType; }>; diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index 3969634c0..0ad855c44 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -32,6 +32,10 @@ export const Query: QueryResolvers = { const source = getSource(args.sourceType); return await source.dataCubeMetadata(parent, args, context, info); }, + dataCubeTermsets: async (parent, args, context, info) => { + const source = getSource(args.sourceType); + return await source.dataCubeTermsets(parent, args, context, info); + }, dataCubeObservations: async (parent, args, context, info) => { const source = getSource(args.sourceType); return await source.dataCubeObservations(parent, args, context, info); diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts index 9e8e87397..3ae98bfb8 100644 --- a/app/graphql/resolvers/rdf.ts +++ b/app/graphql/resolvers/rdf.ts @@ -337,6 +337,18 @@ export const dataCubeComponents: NonNullable< }; }; +export const dataCubeTermsets: NonNullable< + QueryResolvers["dataCubeTermsets"] +> = async (_, { locale, cubeFilter }, { setup }, info) => { + const { sparqlClient } = await setup(info); + const { iri, latest = true } = cubeFilter; + const cube = await new LightCube({ iri, locale, sparqlClient }).init( + !!latest + ); + + return await cube.fetchTermsets(); +}; + export const dataCubeMetadata: NonNullable< QueryResolvers["dataCubeMetadata"] > = async (_, { locale, cubeFilter }, { setup }, info) => { diff --git a/app/graphql/resolvers/sql.ts b/app/graphql/resolvers/sql.ts index 3caa4d41b..719cc4b7c 100644 --- a/app/graphql/resolvers/sql.ts +++ b/app/graphql/resolvers/sql.ts @@ -141,6 +141,12 @@ export const dataCubeMetadata: NonNullable< return {} as any; }; +export const dataCubeTermsets: NonNullable< + QueryResolvers["dataCubeTermsets"] +> = async () => { + return []; +}; + export const possibleFilters: NonNullable< QueryResolvers["possibleFilters"] > = async (_, { iri, filters }) => { diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index b10ce6214..b8697edd8 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -11,6 +11,7 @@ scalar DataCubeComponents scalar DataCubeMetadata scalar DataCubeObservations scalar DataCubePreview +scalar Termset scalar GeoShapes type ObservationsQuery { @@ -330,7 +331,7 @@ enum SearchCubeFilterType { DataCubeTheme DataCubeOrganization DataCubeAbout - SharedDimension + SharedDimensions } input SearchCubeFilter { @@ -345,6 +346,11 @@ enum SearchCubeResultOrder { CREATED_DESC } +input DataCubeTermsetFilter { + iri: String! + latest: Boolean +} + input DataCubeComponentFilter { iri: String! latest: Boolean @@ -381,6 +387,12 @@ type Query { locale: String! cubeFilter: DataCubeComponentFilter! ): DataCubeComponents! + dataCubeTermsets( + sourceType: String! + sourceUrl: String! + locale: String! + cubeFilter: DataCubeTermsetFilter! + ): [Termset!]! dataCubeMetadata( sourceType: String! sourceUrl: String! diff --git a/app/rdf/light-cube.ts b/app/rdf/light-cube.ts index 27f3643fe..dd62a7b2e 100644 --- a/app/rdf/light-cube.ts +++ b/app/rdf/light-cube.ts @@ -7,6 +7,7 @@ import { } from "@/domain/data"; import { getCubeMetadata } from "@/rdf/query-cube-metadata"; import { getCubePreview } from "@/rdf/query-cube-preview"; +import { getCubeTermsets } from "@/rdf/query-cube-termsets"; import { getLatestCubeIriQuery } from "@/rdf/query-latest-cube-iri"; type LightCubeOptions = { @@ -63,6 +64,13 @@ export class LightCube { return this.metadata; } + public async fetchTermsets() { + return await getCubeTermsets(this.iri, { + locale: this.locale, + sparqlClient: this.sparqlClient, + }); + } + public async fetchPreview() { this.preview = await getCubePreview(this.iri, { locale: this.locale, diff --git a/app/rdf/query-cube-termsets.ts b/app/rdf/query-cube-termsets.ts new file mode 100644 index 000000000..b0c0e519f --- /dev/null +++ b/app/rdf/query-cube-termsets.ts @@ -0,0 +1,39 @@ +import ParsingClient from "sparql-http-client/ParsingClient"; + +import { Termset } from "@/domain/data"; +import { buildLocalizedSubQuery } from "@/rdf/query-utils"; + +export const getCubeTermsets = async ( + iri: string, + options: { + locale: string; + sparqlClient: ParsingClient; + } +): Promise => { + const { sparqlClient, locale } = options; + const qs = await sparqlClient.query.select( + `PREFIX cube: +PREFIX meta: +PREFIX rdf: +PREFIX schema: +PREFIX sh: + +SELECT DISTINCT ?termsetIri ?termsetLabel WHERE { + VALUES (?iri) {(<${iri}>)} + + ?iri cube:observationConstraint/sh:property ?dimension . + ?dimension a cube:KeyDimension . + ?dimension sh:in/rdf:rest*/rdf:first ?value. + ?value schema:inDefinedTermSet ?termsetIri . + ?termsetIri a meta:SharedDimension . + + ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })} + +}` + ); + + return qs.map((result) => ({ + iri: result.termsetIri.value, + label: result.termsetLabel.value, + })); +}; diff --git a/app/rdf/query-search.ts b/app/rdf/query-search.ts index 85d8954d4..818217aa8 100644 --- a/app/rdf/query-search.ts +++ b/app/rdf/query-search.ts @@ -89,7 +89,7 @@ const fanOutExclusiveFilters = ( } const { exclusive = [], common = [] } = groupBy(filters, (f) => { - return f.type === SearchCubeFilterType.SharedDimension || + return f.type === SearchCubeFilterType.SharedDimensions || f.type === SearchCubeFilterType.TemporalDimension ? "exclusive" : "common"; @@ -247,7 +247,7 @@ const mkScoresQuery = ( query: string | null | undefined ) => { const searchingSharedDimensions = filters?.some( - (f) => f.type === SearchCubeFilterType.SharedDimension + (f) => f.type === SearchCubeFilterType.SharedDimensions ); return SELECT.DISTINCT` ?iri ?title ?status ?datePublished ?description ?publisher ?creatorIri ?creatorLabel @@ -287,38 +287,19 @@ const mkScoresQuery = ( ?iri ${ns.cube.observationConstraint} ?shape . ?shape ${ns.sh.property} ?prop . ?prop ${ns.cubeMeta.dataKind}/${ns.time.unitType} <${unitNode}>.`; - } else if (df.type === SearchCubeFilterType.SharedDimension) { - const sharedWith = df.value; + } else if (df.type === SearchCubeFilterType.SharedDimensions) { + const sharedDimensions = df.value.split(";"); return sparql` - { - SELECT DISTINCT ?termsetIri WHERE { - VALUES (?iri) {(<${sharedWith}>)} - - ?iri ${ns.cube.observationConstraint}/sh:property ?dimension . - ?dimension a ${ns.cube.KeyDimension} . - ?dimension ${ns.sh.in}/${ns.rdf.first} ?value. - ?value ${ns.schema.inDefinedTermSet} ?termsetIri . - ?termsetIri a ${ns.cubeMeta["SharedDimension"]} . - } - } - `; + VALUES (?termsetIri) {${sharedDimensions.map((sd) => `(<${sd}>)`).join(" ")}} + ?iri a . + ?iri ${ns.cube.observationConstraint}/${ns.sh.property} ?dimension . + ?dimension a ${ns.cube.KeyDimension} . + ?dimension ${ns.sh.in}/${ns.rdf.first} ?value. + ?value ${ns.schema.inDefinedTermSet} ?termsetIri . + ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })}`; } })} - # Shared Dimension - ${ - searchingSharedDimensions - ? sparql`?iri a . - ?iri ${ns.cube.observationConstraint}/${ns.sh.property} ?dimension . - ?dimension a ${ns.cube.KeyDimension} . - ?dimension ${ns.sh.in}/${ns.rdf.first} ?value. - ?value ${ns.schema.inDefinedTermSet} ?termsetIri . - ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })} - ` - : "" - } - - # Publisher, creator status, datePublished OPTIONAL { ?iri ${ns.dcterms.publisher} ?publisher . } ?iri ${ns.schema.creativeWorkStatus} ?status . diff --git a/codegen.yml b/codegen.yml index 264aaaa5b..0f5e00143 100644 --- a/codegen.yml +++ b/codegen.yml @@ -26,6 +26,7 @@ generates: DataCubeMetadata: "../domain/data#DataCubeMetadata" DataCubeObservations: "../domain/data#DataCubeObservations" DataCubePreview: "../domain/data#DataCubePreview" + Termset: "../domain/data#Termset" GeoShapes: "../domain/data#GeoShapes" app/graphql/resolver-types.ts: plugins: