diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b333118..34e28d22e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ You can also check the - Preview mode now takes the real size of the breakpoint, making the area horizontally scrollable in case the screen size is smaller than the breakpoint + - It's now possible to simultaneously select browse filters from every panel + (themes, organizations, concepts) - Fixes - Interactive filters in the published mode and now only showing combinations of filters that are actually present in the data, taking configurator @@ -27,6 +29,8 @@ You can also check the - Compact table view margins were improved so rows do not overlap - Chart title and dataset tooltips in the user profile now only shows when the title is truncated + - Concepts navigation on the browse page now shows the correct number of + results # [4.8.0] - 2024-09-11 diff --git a/app/browser/context.tsx b/app/browser/context.tsx index 72626ba9c..4bc04c1ed 100644 --- a/app/browser/context.tsx +++ b/app/browser/context.tsx @@ -34,6 +34,8 @@ export const getBrowseParamsFromQuery = ( "iri", "subtype", "subiri", + "subsubtype", + "subsubiri", "topic", "includeDrafts", "order", @@ -44,8 +46,17 @@ export const getBrowseParamsFromQuery = ( (v) => (Array.isArray(v) ? v[0] : v) ); - const { type, iri, subtype, subiri, topic, includeDrafts, ...values } = - rawValues; + const { + type, + iri, + subtype, + subiri, + subsubtype, + subsubiri, + topic, + includeDrafts, + ...values + } = rawValues; const previous = values.previous ? JSON.parse(values.previous) : undefined; return pickBy( @@ -55,6 +66,8 @@ export const getBrowseParamsFromQuery = ( iri: iri ?? previous?.iri, subtype: subtype ?? previous?.subtype, subiri: subiri ?? previous?.subiri, + subsubtype: subsubtype ?? previous?.subsubtype, + subsubiri: subsubiri ?? previous?.subsubiri, topic: topic ?? previous?.topic, includeDrafts: includeDrafts ? JSON.parse(includeDrafts) : undefined, }, @@ -63,7 +76,8 @@ export const getBrowseParamsFromQuery = ( }; export const buildURLFromBrowseState = (browseState: BrowseParams) => { - const { type, iri, subtype, subiri, ...queryParams } = browseState; + const { type, iri, subtype, subiri, subsubtype, subsubiri, ...queryParams } = + browseState; const typePart = type && iri @@ -73,8 +87,14 @@ export const buildURLFromBrowseState = (browseState: BrowseParams) => { subtype && subiri ? `${encodeURIComponent(subtype)}/${encodeURIComponent(subiri)}` : undefined; + const subsubtypePart = + subsubtype && subsubiri + ? `${encodeURIComponent(subsubtype)}/${encodeURIComponent(subsubiri)}` + : undefined; - const pathname = ["/browse", typePart, subtypePart].filter(Boolean).join("/"); + const pathname = ["/browse", typePart, subtypePart, subsubtypePart] + .filter(Boolean) + .join("/"); return { pathname, query: queryParams, diff --git a/app/browser/dataset-browse.tsx b/app/browser/dataset-browse.tsx index 6d60b0858..38548c2e7 100644 --- a/app/browser/dataset-browse.tsx +++ b/app/browser/dataset-browse.tsx @@ -21,7 +21,7 @@ import uniqBy from "lodash/uniqBy"; import Link from "next/link"; import { useRouter } from "next/router"; import { stringify } from "qs"; -import React, { ComponentProps, useMemo, useState } from "react"; +import React, { ComponentProps, ReactNode, useMemo, useState } from "react"; import Flex, { FlexProps } from "@/components/flex"; import { @@ -41,15 +41,15 @@ import { } from "@/components/presence"; import Tag from "@/components/tag"; import useDisclosure from "@/components/use-disclosure"; -import { SearchCube, Termset } from "@/domain/data"; +import { SearchCube } from "@/domain/data"; import { truthy } from "@/domain/types"; import { useFlag } from "@/flags"; import { useFormatDate } from "@/formatters"; import { DataCubeOrganization, + DataCubeTermset, DataCubeTheme, SearchCubeResultOrder, - TermsetCount, } from "@/graphql/query-hooks"; import { DataCubePublicationStatus, @@ -355,7 +355,7 @@ const encodeFilter = (filter: BrowseFilter) => { return "organization"; case "DataCubeAbout": return "topic"; - case "Termset": + case "DataCubeTermset": return "termset"; default: const check: never = __typename; @@ -375,7 +375,7 @@ const NavItem = ({ level = 1, disableLink, }: { - children: React.ReactNode; + children: ReactNode; filters: BrowseFilter[]; next: BrowseFilter; count?: number; @@ -611,10 +611,10 @@ const NavSection = ({ }: { label: React.ReactNode; icon: React.ReactNode; - items: (DataCubeTheme | DataCubeOrganization | Termset)[]; + items: (DataCubeTheme | DataCubeOrganization | DataCubeTermset)[]; theme: { backgroundColor: string; borderColor: string }; navItemTheme: NavItemTheme; - currentFilter?: DataCubeTheme | DataCubeOrganization | Termset; + currentFilter?: DataCubeTheme | DataCubeOrganization | DataCubeTermset; filters: BrowseFilter[]; counts: Record; extra?: React.ReactNode; @@ -702,48 +702,10 @@ const NavSection = ({ ); }; -const TermsetNavSection = ({ - currentFilter, - termsetCounts, - disableLinks, -}: { - currentFilter: Termset | undefined; - termsetCounts: { termset: Termset; count: number }[]; - disableLinks: boolean; -}) => { - const { counts, termsets } = useMemo(() => { - const termsets = termsetCounts.map((x) => x.termset) ?? []; - const counts = Object.fromEntries( - termsetCounts.map((x) => [x.termset.iri, x.count]) - ); - return { - counts, - termsets: sortBy(termsets, (t) => t.label), - }; - }, [termsetCounts]); - - return ( - } - label={Concepts} - filters={[]} - counts={counts} - disableLinks={disableLinks} - /> - ); -}; - const navOrder: Record = { DataCubeTheme: 1, DataCubeOrganization: 2, - Termset: 3, + DataCubeTermset: 3, // Not used in the nav DataCubeAbout: 4, }; @@ -752,13 +714,13 @@ export const SearchFilters = ({ cubes, themes, orgs, - termsets: termsetCounts, + termsets, disableNavLinks = false, }: { cubes: SearchCubeResult[]; themes: DataCubeTheme[]; orgs: DataCubeOrganization[]; - termsets: TermsetCount[]; + termsets: DataCubeTermset[]; disableNavLinks?: boolean; }) => { const { filters } = useBrowseContext(); @@ -766,13 +728,14 @@ export const SearchFilters = ({ const result: Record = {}; for (const { cube } of cubes) { - const countables = [ + const countable = [ ...cube.themes, ...cube.subthemes, + ...cube.termsets, cube.creator, ].filter(truthy); - for (const { iri } of countables) { + for (const { iri } of countable) { if (iri) { result[iri] = (result[iri] ?? 0) + 1; } @@ -785,12 +748,17 @@ export const SearchFilters = ({ const { DataCubeTheme: themeFilter, DataCubeOrganization: orgFilter, - Termset: termsetFilter, + DataCubeTermset: termsetFilter, } = useMemo(() => { - return keyBy(filters, (f) => f.__typename) as { - DataCubeTheme?: DataCubeTheme; - DataCubeOrganization?: DataCubeOrganization; - Termset?: Termset; + const result = keyBy(filters, (f) => f.__typename) as { + [K in BrowseFilter["__typename"]]?: BrowseFilter; + }; + return { + DataCubeTheme: result.DataCubeTheme as DataCubeTheme | undefined, + DataCubeOrganization: result.DataCubeOrganization as + | DataCubeOrganization + | undefined, + DataCubeTermset: result.DataCubeTermset as DataCubeTermset | undefined, }; }, [filters]); @@ -826,6 +794,22 @@ export const SearchFilters = ({ return true; }); + const displayedTermsets = termsets.filter((termset) => { + if (!termset.label) { + return false; + } + + if (!counts[termset.iri] && termsetFilter?.iri !== termset.iri) { + return false; + } + + if (termsetFilter && termsetFilter.iri !== termset.iri) { + return false; + } + + return true; + }); + const themeNav = displayedThemes && displayedThemes.length > 0 ? ( } + label={Concepts} + extra={null} disableLinks={disableNavLinks} /> ); - const navs = sortBy( - [ - { element: themeNav, __typename: "DataCubeTheme" }, - { element: orgNav, __typename: "DataCubeOrganization" }, - { element: termsetNav, __typename: "Termset" }, - ], - (x) => - x.__typename === filters[0]?.__typename - ? 0 - : navOrder[x.__typename as keyof typeof navOrder] - ); + const baseNavs: { + element: ReactNode; + __typename: BrowseFilter["__typename"]; + }[] = [ + { element: themeNav, __typename: "DataCubeTheme" }, + { element: orgNav, __typename: "DataCubeOrganization" }, + { element: termsetNav, __typename: "DataCubeTermset" }, + ]; + const navs = sortBy(baseNavs, (x) => { + const i = filters.findIndex((f) => f.__typename === x.__typename); + return i === -1 + ? // If the filter is not in the list, we want to put it at the end + navOrder[x.__typename as BrowseFilter["__typename"]] + + Object.keys(navOrder).length + : i; + }); return ( & { label?: string }); + | DataCubeTermset; /** Builds the state search filters from query params */ export const getFiltersFromParams = (params: BrowseParams) => { const filters: BrowseFilter[] = []; - const { type, subtype, iri, subiri, topic } = params; + const { type, subtype, subsubtype, iri, subiri, subsubiri, topic } = params; for (const [t, i] of [ [type, iri], [subtype, subiri], + [subsubtype, subsubiri], ]) { if (t && i && (t === "theme" || t === "organization" || t === "termset")) { const __typename = (() => { @@ -34,7 +35,7 @@ export const getFiltersFromParams = (params: BrowseParams) => { case "organization": return SearchCubeFilterType.DataCubeOrganization; case "termset": - return SearchCubeFilterType.Termset; + return SearchCubeFilterType.DataCubeTermset; } })(); filters.push({ @@ -58,14 +59,16 @@ export const getParamsFromFilters = (filters: BrowseFilter[]) => { const params: BrowseParams = { type: undefined, subtype: undefined, + subsubtype: undefined, iri: undefined, subiri: undefined, + subsubiri: undefined, topic: undefined, }; let i = 0; for (const filter of filters) { - const typeAttr = i === 0 ? "type" : ("subtype" as const); - const iriAttr = i === 0 ? "iri" : ("subiri" as const); + const typeAttr = i === 0 ? "type" : i === 1 ? "subtype" : "subsubtype"; + const iriAttr = i === 0 ? "iri" : i === 1 ? "subiri" : "subsubiri"; switch (filter.__typename) { case "DataCubeTheme": params[typeAttr] = "theme"; @@ -78,7 +81,7 @@ export const getParamsFromFilters = (filters: BrowseFilter[]) => { case "DataCubeAbout": params.topic = filter.iri; break; - case "Termset": + case "DataCubeTermset": params[typeAttr] = "termset"; params[iriAttr] = filter.iri; break; diff --git a/app/browser/search-page-data.tsx b/app/browser/search-page-data.tsx deleted file mode 100644 index 1da1e1c74..000000000 --- a/app/browser/search-page-data.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useSearchPageQuery } from "@/graphql/query-hooks"; -import { useConfiguratorState, useLocale } from "@/src"; - -export const useSearchPageData = () => { - const locale = useLocale(); - const [configState] = useConfiguratorState(); - const { dataSource } = configState; - const [searchPageQuery] = useSearchPageQuery({ - variables: { - sourceType: dataSource.type, - sourceUrl: dataSource.url, - locale, - }, - }); - return searchPageQuery; -}; diff --git a/app/browser/select-dataset-step.tsx b/app/browser/select-dataset-step.tsx index 45f1b5586..062f1af43 100644 --- a/app/browser/select-dataset-step.tsx +++ b/app/browser/select-dataset-step.tsx @@ -24,7 +24,6 @@ import { } from "@/browser/dataset-browse"; import { DataSetPreview, DataSetPreviewProps } from "@/browser/dataset-preview"; import { BrowseFilter, DataCubeAbout } from "@/browser/filters"; -import { useSearchPageData } from "@/browser/search-page-data"; import { DatasetMetadata } from "@/components/dataset-metadata"; import Flex from "@/components/flex"; import { Footer } from "@/components/footer"; @@ -46,6 +45,7 @@ import { import { truthy } from "@/domain/types"; import { DataCubeOrganization, + DataCubeTermset, DataCubeTheme, SearchCubeFilterType, useDataCubeMetadataQuery, @@ -274,11 +274,17 @@ const SelectDatasetStepContent = ({ ); }, [cubes]); - const searchPageData = useSearchPageData(); - const termsets = useMemo( - () => searchPageData.data?.allTermsets ?? [], - [searchPageData.data?.allTermsets] - ); + const termsets: DataCubeTermset[] = useMemo(() => { + return sortBy( + uniqBy( + cubes.flatMap((d) => + d.cube.termsets.map((d) => ({ ...d, __typename: "DataCubeTermset" })) + ), + (d) => d.iri + ), + (d) => d.label + ); + }, [cubes]); const pageTitle = useMemo(() => { return queryFilters @@ -290,8 +296,8 @@ const SelectDatasetStepContent = ({ return themes; case SearchCubeFilterType.DataCubeOrganization: return orgs; - case SearchCubeFilterType.Termset: - return termsets.map((x) => x.termset); + case SearchCubeFilterType.DataCubeTermset: + return termsets; case SearchCubeFilterType.DataCubeAbout: return []; case SearchCubeFilterType.TemporalDimension: diff --git a/app/components/graphql-search.stories.tsx b/app/components/graphql-search.stories.tsx index 95de0bbea..8bdba770a 100644 --- a/app/components/graphql-search.stories.tsx +++ b/app/components/graphql-search.stories.tsx @@ -92,7 +92,7 @@ export const Search = () => { filters: [ sharedComponents ? { - type: SearchCubeFilterType.Termset, + type: SearchCubeFilterType.DataCubeTermset, value: sharedComponents .map((x) => cubeSharedDimensionsByIri[x]) .filter(truthy) @@ -108,6 +108,7 @@ export const Search = () => { : null, ].filter(truthy), includeDrafts: false, + fetchDimensionTermsets: true, query, }, }); diff --git a/app/configurator/components/add-dataset-dialog.tsx b/app/configurator/components/add-dataset-dialog.tsx index 70eebb022..5e5b958e4 100644 --- a/app/configurator/components/add-dataset-dialog.tsx +++ b/app/configurator/components/add-dataset-dialog.tsx @@ -714,6 +714,7 @@ export const DatasetDialog = ({ query: query, order: SearchCubeResultOrder.Score, includeDrafts: false, + fetchDimensionTermsets: true, filters: [ selectedTemporalDimension ? { @@ -723,7 +724,7 @@ export const DatasetDialog = ({ : null, selectedSharedDimensions && selectedSharedDimensions.length > 0 ? { - type: SearchCubeFilterType.Termset, + type: SearchCubeFilterType.DataCubeTermset, value: uniq( selectedSharedDimensions.flatMap((x) => x.termsets.map((x) => x.iri) diff --git a/app/domain/data.ts b/app/domain/data.ts index c230b1260..40d5c049b 100644 --- a/app/domain/data.ts +++ b/app/domain/data.ts @@ -406,6 +406,10 @@ export type SearchCube = { iri: string; label: string; }[]; + termsets: { + iri: string; + label: string; + }[]; dimensions?: { iri: string; label: string; diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index 291e6f307..8eabd91c6 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -1,10 +1,3 @@ -query SearchPage($sourceType: String!, $sourceUrl: String!, $locale: String!) { - allTermsets(sourceType: $sourceType, sourceUrl: $sourceUrl, locale: $locale) { - count - termset - } -} - query SearchCubes( $sourceType: String! $sourceUrl: String! @@ -12,6 +5,7 @@ query SearchCubes( $query: String $order: SearchCubeResultOrder $includeDrafts: Boolean + $fetchDimensionTermsets: Boolean $filters: [SearchCubeFilter!] ) { searchCubes( @@ -21,6 +15,7 @@ query SearchCubes( query: $query order: $order includeDrafts: $includeDrafts + fetchDimensionTermsets: $fetchDimensionTermsets filters: $filters ) { highlightedTitle diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index ddbe69348..ed3d2c97a 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -98,6 +98,12 @@ export enum DataCubePublicationStatus { Published = 'PUBLISHED' } +export type DataCubeTermset = { + __typename: 'DataCubeTermset'; + iri: Scalars['String']; + label?: Maybe; +}; + export type DataCubeTermsetFilter = { iri: Scalars['String']; }; @@ -132,7 +138,6 @@ export type Query = { possibleFilters: Array; searchCubes: Array; dataCubeDimensionGeoShapes?: Maybe; - allTermsets: Array; }; @@ -197,6 +202,7 @@ export type QuerySearchCubesArgs = { query?: Maybe; order?: Maybe; includeDrafts?: Maybe; + fetchDimensionTermsets?: Maybe; filters?: Maybe>; }; @@ -209,14 +215,6 @@ export type QueryDataCubeDimensionGeoShapesArgs = { }; -export type QueryAllTermsetsArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - includeDrafts?: Maybe; -}; - - export type RelatedDimension = { __typename: 'RelatedDimension'; type: Scalars['String']; @@ -242,7 +240,7 @@ export enum SearchCubeFilterType { DataCubeTheme = 'DataCubeTheme', DataCubeOrganization = 'DataCubeOrganization', DataCubeAbout = 'DataCubeAbout', - Termset = 'Termset' + DataCubeTermset = 'DataCubeTermset' } export type SearchCubeResult = { @@ -261,12 +259,6 @@ export enum SearchCubeResultOrder { -export type TermsetCount = { - __typename: 'TermsetCount'; - termset: Scalars['Termset']; - count: Scalars['Int']; -}; - export enum TimeUnit { Year = 'Year', Month = 'Month', @@ -279,6 +271,20 @@ export enum TimeUnit { +export type SearchCubesQueryVariables = Exact<{ + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; + locale: Scalars['String']; + query?: Maybe; + order?: Maybe; + includeDrafts?: Maybe; + fetchDimensionTermsets?: Maybe; + filters?: Maybe | SearchCubeFilter>; +}>; + + +export type SearchCubesQuery = { __typename: 'Query', searchCubes: Array<{ __typename: 'SearchCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, cube: SearchCube }> }; + export type DataCubeLatestIriQueryVariables = Exact<{ sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -298,6 +304,16 @@ export type DataCubeComponentsQueryVariables = Exact<{ export type DataCubeComponentsQuery = { __typename: 'Query', dataCubeComponents: DataCubeComponents }; +export type DataCubeDimensionGeoShapesQueryVariables = Exact<{ + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; + locale: Scalars['String']; + cubeFilter: DataCubeDimensionGeoShapesCubeFilter; +}>; + + +export type DataCubeDimensionGeoShapesQuery = { __typename: 'Query', dataCubeDimensionGeoShapes?: Maybe }; + export type DataCubeMetadataQueryVariables = Exact<{ sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -338,19 +354,6 @@ export type DataCubePreviewQueryVariables = Exact<{ export type DataCubePreviewQuery = { __typename: 'Query', dataCubePreview: DataCubePreview }; -export type SearchCubesQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - query?: Maybe; - order?: Maybe; - includeDrafts?: Maybe; - filters?: Maybe | SearchCubeFilter>; -}>; - - -export type SearchCubesQuery = { __typename: 'Query', searchCubes: Array<{ __typename: 'SearchCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, cube: SearchCube }> }; - export type PossibleFiltersQueryVariables = Exact<{ sourceType: Scalars['String']; sourceUrl: Scalars['String']; @@ -360,26 +363,29 @@ export type PossibleFiltersQueryVariables = Exact<{ export type PossibleFiltersQuery = { __typename: 'Query', possibleFilters: Array<{ __typename: 'ObservationFilter', iri: string, type: string, value?: Maybe }> }; -export type DataCubeDimensionGeoShapesQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - cubeFilter: DataCubeDimensionGeoShapesCubeFilter; -}>; - - -export type DataCubeDimensionGeoShapesQuery = { __typename: 'Query', dataCubeDimensionGeoShapes?: Maybe }; - -export type SearchPageQueryVariables = Exact<{ - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; -}>; - - -export type SearchPageQuery = { __typename: 'Query', allTermsets: Array<{ __typename: 'TermsetCount', count: number, termset: Termset }> }; +export const SearchCubesDocument = gql` + query SearchCubes($sourceType: String!, $sourceUrl: String!, $locale: String!, $query: String, $order: SearchCubeResultOrder, $includeDrafts: Boolean, $fetchDimensionTermsets: Boolean, $filters: [SearchCubeFilter!]) { + searchCubes( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + query: $query + order: $order + includeDrafts: $includeDrafts + fetchDimensionTermsets: $fetchDimensionTermsets + filters: $filters + ) { + highlightedTitle + highlightedDescription + cube + } +} + `; +export function useSearchCubesQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: SearchCubesDocument, ...options }); +}; export const DataCubeLatestIriDocument = gql` query DataCubeLatestIri($sourceType: String!, $sourceUrl: String!, $cubeFilter: DataCubeLatestIriFilter!) { dataCubeLatestIri( @@ -407,6 +413,20 @@ export const DataCubeComponentsDocument = gql` export function useDataCubeComponentsQuery(options: Omit, 'query'> = {}) { return Urql.useQuery({ query: DataCubeComponentsDocument, ...options }); }; +export const DataCubeDimensionGeoShapesDocument = gql` + query DataCubeDimensionGeoShapes($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubeDimensionGeoShapesCubeFilter!) { + dataCubeDimensionGeoShapes( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) +} + `; + +export function useDataCubeDimensionGeoShapesQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeDimensionGeoShapesDocument, ...options }); +}; export const DataCubeMetadataDocument = gql` query DataCubeMetadata($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubeMetadataFilter!) { dataCubeMetadata( @@ -424,10 +444,10 @@ export function useDataCubeMetadataQuery(options: Omit, 'query'> = {}) { return Urql.useQuery({ query: DataCubePreviewDocument, ...options }); }; -export const SearchCubesDocument = gql` - query SearchCubes($sourceType: String!, $sourceUrl: String!, $locale: String!, $query: String, $order: SearchCubeResultOrder, $includeDrafts: Boolean, $filters: [SearchCubeFilter!]) { - searchCubes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - query: $query - order: $order - includeDrafts: $includeDrafts - filters: $filters - ) { - highlightedTitle - highlightedDescription - cube - } -} - `; - -export function useSearchCubesQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: SearchCubesDocument, ...options }); -}; export const PossibleFiltersDocument = gql` query PossibleFilters($sourceType: String!, $sourceUrl: String!, $cubeFilter: DataCubePossibleFiltersCubeFilter!) { possibleFilters( @@ -500,30 +499,4 @@ export const PossibleFiltersDocument = gql` export function usePossibleFiltersQuery(options: Omit, 'query'> = {}) { return Urql.useQuery({ query: PossibleFiltersDocument, ...options }); -}; -export const DataCubeDimensionGeoShapesDocument = gql` - query DataCubeDimensionGeoShapes($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubeDimensionGeoShapesCubeFilter!) { - dataCubeDimensionGeoShapes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) -} - `; - -export function useDataCubeDimensionGeoShapesQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: DataCubeDimensionGeoShapesDocument, ...options }); -}; -export const SearchPageDocument = gql` - query SearchPage($sourceType: String!, $sourceUrl: String!, $locale: String!) { - allTermsets(sourceType: $sourceType, sourceUrl: $sourceUrl, locale: $locale) { - count - termset - } -} - `; - -export function useSearchPageQuery(options: Omit, 'query'> = {}) { - return Urql.useQuery({ query: SearchPageDocument, ...options }); }; \ No newline at end of file diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index 26e301d0f..b6dd0a1e8 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -98,6 +98,12 @@ export enum DataCubePublicationStatus { Published = 'PUBLISHED' } +export type DataCubeTermset = { + __typename?: 'DataCubeTermset'; + iri: Scalars['String']; + label?: Maybe; +}; + export type DataCubeTermsetFilter = { iri: Scalars['String']; }; @@ -132,7 +138,6 @@ export type Query = { possibleFilters: Array; searchCubes: Array; dataCubeDimensionGeoShapes?: Maybe; - allTermsets: Array; }; @@ -197,6 +202,7 @@ export type QuerySearchCubesArgs = { query?: Maybe; order?: Maybe; includeDrafts?: Maybe; + fetchDimensionTermsets?: Maybe; filters?: Maybe>; }; @@ -209,14 +215,6 @@ export type QueryDataCubeDimensionGeoShapesArgs = { }; -export type QueryAllTermsetsArgs = { - sourceType: Scalars['String']; - sourceUrl: Scalars['String']; - locale: Scalars['String']; - includeDrafts?: Maybe; -}; - - export type RelatedDimension = { __typename?: 'RelatedDimension'; type: Scalars['String']; @@ -242,7 +240,7 @@ export enum SearchCubeFilterType { DataCubeTheme = 'DataCubeTheme', DataCubeOrganization = 'DataCubeOrganization', DataCubeAbout = 'DataCubeAbout', - Termset = 'Termset' + DataCubeTermset = 'DataCubeTermset' } export type SearchCubeResult = { @@ -261,12 +259,6 @@ export enum SearchCubeResultOrder { -export type TermsetCount = { - __typename?: 'TermsetCount'; - termset: Scalars['Termset']; - count: Scalars['Int']; -}; - export enum TimeUnit { Year = 'Year', Month = 'Month', @@ -361,6 +353,7 @@ export type ResolversTypes = ResolversObject<{ DataCubePreview: ResolverTypeWrapper; DataCubePreviewFilter: DataCubePreviewFilter; DataCubePublicationStatus: DataCubePublicationStatus; + DataCubeTermset: ResolverTypeWrapper; DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: ResolverTypeWrapper; DimensionValue: ResolverTypeWrapper; @@ -382,8 +375,6 @@ export type ResolversTypes = ResolversObject<{ SearchCubeResultOrder: SearchCubeResultOrder; SingleFilters: ResolverTypeWrapper; Termset: ResolverTypeWrapper; - TermsetCount: ResolverTypeWrapper; - Int: ResolverTypeWrapper; TimeUnit: TimeUnit; ValueIdentifier: ResolverTypeWrapper; ValuePosition: ResolverTypeWrapper; @@ -406,6 +397,7 @@ export type ResolversParentTypes = ResolversObject<{ DataCubePossibleFiltersCubeFilter: DataCubePossibleFiltersCubeFilter; DataCubePreview: Scalars['DataCubePreview']; DataCubePreviewFilter: DataCubePreviewFilter; + DataCubeTermset: DataCubeTermset; DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: DataCubeTheme; DimensionValue: Scalars['DimensionValue']; @@ -424,8 +416,6 @@ export type ResolversParentTypes = ResolversObject<{ Float: Scalars['Float']; SingleFilters: Scalars['SingleFilters']; Termset: Scalars['Termset']; - TermsetCount: TermsetCount; - Int: Scalars['Int']; ValueIdentifier: Scalars['ValueIdentifier']; ValuePosition: Scalars['ValuePosition']; }>; @@ -456,6 +446,12 @@ export interface DataCubePreviewScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ + iri?: Resolver; + label?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type DataCubeThemeResolvers = ResolversObject<{ iri?: Resolver; label?: Resolver, ParentType, ContextType>; @@ -503,7 +499,6 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; searchCubes?: Resolver, ParentType, ContextType, RequireFields>; dataCubeDimensionGeoShapes?: Resolver, ParentType, ContextType, RequireFields>; - allTermsets?: Resolver, ParentType, ContextType, RequireFields>; }>; export interface RawObservationScalarConfig extends GraphQLScalarTypeConfig { @@ -536,12 +531,6 @@ export interface TermsetScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ - termset?: Resolver; - count?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - export interface ValueIdentifierScalarConfig extends GraphQLScalarTypeConfig { name: 'ValueIdentifier'; } @@ -557,6 +546,7 @@ export type Resolvers = ResolversObject<{ DataCubeObservations?: GraphQLScalarType; DataCubeOrganization?: DataCubeOrganizationResolvers; DataCubePreview?: GraphQLScalarType; + DataCubeTermset?: DataCubeTermsetResolvers; DataCubeTheme?: DataCubeThemeResolvers; DimensionValue?: GraphQLScalarType; FilterValue?: GraphQLScalarType; @@ -572,7 +562,6 @@ export type Resolvers = ResolversObject<{ SearchCubeResult?: SearchCubeResultResolvers; SingleFilters?: GraphQLScalarType; Termset?: GraphQLScalarType; - TermsetCount?: TermsetCountResolvers; ValueIdentifier?: GraphQLScalarType; ValuePosition?: GraphQLScalarType; }>; diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index d90298107..ef12f7f15 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -51,10 +51,6 @@ export const Query: QueryResolvers = { const source = getSource(args.sourceType); return await source.dataCubeDimensionGeoShapes(parent, args, context, info); }, - allTermsets: async (parent, args, context, info) => { - const source = getSource(args.sourceType); - return await source.allTermsets(parent, args, context, info); - }, }; export const resolveDimensionType = ( diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts index 5716ac9fb..76330fef2 100644 --- a/app/graphql/resolvers/rdf.ts +++ b/app/graphql/resolvers/rdf.ts @@ -37,7 +37,6 @@ import { parseHierarchy, queryHierarchies } from "@/rdf/query-hierarchies"; import { queryLatestCubeIri } from "@/rdf/query-latest-cube-iri"; import { getPossibleFilters } from "@/rdf/query-possible-filters"; import { SearchResult, searchCubes as _searchCubes } from "@/rdf/query-search"; -import { queryAllTermsets } from "@/rdf/query-termsets"; import { getSparqlEditorUrl } from "@/rdf/sparql-utils"; export const dataCubeLatestIri: NonNullable< @@ -81,7 +80,7 @@ const sortResults = ( export const searchCubes: NonNullable = async ( _, - { locale, query, order, includeDrafts, filters }, + { locale, query, order, includeDrafts, fetchDimensionTermsets, filters }, { setup }, info ) => { @@ -89,6 +88,7 @@ export const searchCubes: NonNullable = async ( const cubes = await _searchCubes({ locale, includeDrafts, + fetchDimensionTermsets, filters, query, sparqlClient, @@ -429,13 +429,3 @@ const getDimensionValuesLoader = ( return loaders.dimensionValues; } }; - -export const allTermsets: NonNullable = async ( - _, - { locale }, - { setup }, - info -) => { - const { sparqlClient } = await setup(info); - return await queryAllTermsets({ locale, sparqlClient }); -}; diff --git a/app/graphql/resolvers/sql.ts b/app/graphql/resolvers/sql.ts index aa4e85fed..18b8e0f77 100644 --- a/app/graphql/resolvers/sql.ts +++ b/app/graphql/resolvers/sql.ts @@ -219,9 +219,3 @@ export const dataCubePreview: NonNullable< observations: [], }; }; - -export const allTermsets: NonNullable< - QueryResolvers["allTermsets"] -> = async () => { - return []; -}; diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index e0897f52d..b83eaf1f8 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -14,6 +14,7 @@ scalar DataCubePreview scalar Termset scalar ComponentTermsets scalar GeoShapes +scalar SearchCube enum DataCubePublicationStatus { DRAFT @@ -48,8 +49,6 @@ enum TimeUnit { Second } -scalar SearchCube - type SearchCubeResult { score: Float cube: SearchCube! @@ -67,12 +66,17 @@ type DataCubeOrganization { label: String } +type DataCubeTermset { + iri: String! + label: String +} + enum SearchCubeFilterType { TemporalDimension DataCubeTheme DataCubeOrganization DataCubeAbout - Termset + DataCubeTermset } input SearchCubeFilter { @@ -128,11 +132,6 @@ input DataCubeDimensionGeoShapesCubeFilter { dimensionIri: String! } -type TermsetCount { - termset: Termset! - count: Int! -} - # The "Query" type is special: it lists all of the available queries that # clients can execute, along with the return type for each. type Query { @@ -185,6 +184,7 @@ type Query { query: String order: SearchCubeResultOrder includeDrafts: Boolean + fetchDimensionTermsets: Boolean filters: [SearchCubeFilter!] ): [SearchCubeResult!]! dataCubeDimensionGeoShapes( @@ -193,10 +193,4 @@ type Query { locale: String! cubeFilter: DataCubeDimensionGeoShapesCubeFilter! ): GeoShapes - allTermsets( - sourceType: String! - sourceUrl: String! - locale: String! - includeDrafts: Boolean - ): [TermsetCount!]! } diff --git a/app/pages/browse/[type]/[iri]/[subtype].tsx b/app/pages/browse/[type]/[iri]/[subtype].tsx new file mode 100644 index 000000000..f625bc14b --- /dev/null +++ b/app/pages/browse/[type]/[iri]/[subtype].tsx @@ -0,0 +1,3 @@ +import { DatasetBrowser } from "@/pages/browse"; + +export default DatasetBrowser; diff --git a/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype].tsx b/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype].tsx new file mode 100644 index 000000000..f625bc14b --- /dev/null +++ b/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype].tsx @@ -0,0 +1,3 @@ +import { DatasetBrowser } from "@/pages/browse"; + +export default DatasetBrowser; diff --git a/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype]/[subsubiri].tsx b/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype]/[subsubiri].tsx new file mode 100644 index 000000000..f625bc14b --- /dev/null +++ b/app/pages/browse/[type]/[iri]/[subtype]/[subiri]/[subsubtype]/[subsubiri].tsx @@ -0,0 +1,3 @@ +import { DatasetBrowser } from "@/pages/browse"; + +export default DatasetBrowser; diff --git a/app/pages/browse/index.tsx b/app/pages/browse/index.tsx index 4a0f8efa5..2807b543b 100644 --- a/app/pages/browse/index.tsx +++ b/app/pages/browse/index.tsx @@ -6,8 +6,10 @@ import { SearchCubeResultOrder } from "@/graphql/query-hooks"; export type BrowseParams = { type?: "theme" | "organization" | "dataset" | "termset"; subtype?: "theme" | "organization" | "termset"; + subsubtype?: "theme" | "organization" | "termset"; iri?: string; subiri?: string; + subsubiri?: string; topic?: string; search?: string; order?: SearchCubeResultOrder; diff --git a/app/rdf/parse-search-results.spec.ts b/app/rdf/parse-search-results.spec.ts index 8fca413bb..b12d8f0b8 100644 --- a/app/rdf/parse-search-results.spec.ts +++ b/app/rdf/parse-search-results.spec.ts @@ -18,7 +18,7 @@ const parseCSV = async (filepath: string) => { ); // Parse the rows into subject, predicate, and object - const rows = csvParse(csvData).slice(1); + const rows = csvParse(csvData); return rows.map((row) => { return { subject: { value: row.subject! }, @@ -29,7 +29,55 @@ const parseCSV = async (filepath: string) => { }; describe("parse-search-results", () => { - it("should build search cubes from CSV (shared dimension query)", async () => { + it("should correctly parse cube", async () => { + const data = await parseCSV( + path.join( + __dirname, + "./query-search-results-photovoltaikanlagen.mock.csv" + ) + ); + const searchCubes = buildSearchCubes(data); + + expect(searchCubes).toMatchInlineSnapshot(` + Array [ + Object { + "creator": Object { + "iri": "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe", + "label": "Swiss Federal Office of Energy SFOE", + }, + "datePublished": "2022-08-16", + "description": "Seit 2014 werden Photovoltaikanlagen mit einer Einmalvergütung (EIV) gefördert. Dabei wird abhängig von der Leistung, der Anlagenkategorie und dem Inbetriebnahmedatum ein einmaliger Beitrag an die Anlagenbetreiber ausbezahlt. Hier finden Sie pro Kanton und Auszahlungsjahr einen Überblick über die Anzahl geförderter EIV-Anlagen, die installierte Leistung in Kilowatt (kW) sowie den ausbezahlten EIV-Förderbeitrag. Die dargestellten Daten entsprechen nicht vollständig der offiziellen Statistik der erneuerbaren Energien durch das BFE. Da der Abbau der Wartelisten zeitverzögert stattfindet, können Abweichungen entstehen.", + "dimensions": undefined, + "iri": "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10", + "publicationStatus": "PUBLISHED", + "subthemes": Array [], + "termsets": Array [ + Object { + "iri": "https://ld.admin.ch/dimension/office", + "label": "Federal Offices", + }, + Object { + "iri": "https://ld.admin.ch/dimension/canton", + "label": "Cantons", + }, + ], + "themes": Array [ + Object { + "iri": "https://register.ld.admin.ch/opendataswiss/category/energy", + "label": "Energy", + }, + Object { + "iri": "https://register.ld.admin.ch/opendataswiss/category/national-economy", + "label": "National economy", + }, + ], + "title": "Einmalvergütung für Photovoltaikanlagen", + }, + ] + `); + }); + + it("should build search cubes from CSV (shared dimension query, all)", async () => { const data = await parseCSV( path.join(__dirname, "./query-search-results-shared-dimensions.mock.csv") ); @@ -38,6 +86,33 @@ describe("parse-search-results", () => { expect(searchCubes.slice(0, 3)).toMatchInlineSnapshot(` Array [ + Object { + "creator": Object { + "iri": "https://register.ld.admin.ch/opendataswiss/org/elcom", + "label": "Federal Electricity Commission ElCom", + }, + "datePublished": "2021-01-01", + "description": "Median electricity tariff per region & consumption profiles", + "dimensions": Array [ + Object { + "iri": "https://energy.ld.admin.ch/elcom/electricityprice/dimension/canton", + "label": "Canton", + "termsets": Array [ + Object { + "iri": "https://ld.admin.ch/dimension/canton", + "label": "Cantons", + }, + ], + "timeUnit": "", + }, + ], + "iri": "https://energy.ld.admin.ch/elcom/electricityprice-canton", + "publicationStatus": "PUBLISHED", + "subthemes": Array [], + "termsets": Array [], + "themes": Array [], + "title": "Median electricity tariff per canton", + }, Object { "creator": Object { "iri": "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe", @@ -61,6 +136,7 @@ describe("parse-search-results", () => { "iri": "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14", "publicationStatus": "PUBLISHED", "subthemes": Array [], + "termsets": Array [], "themes": Array [ Object { "iri": "https://register.ld.admin.ch/opendataswiss/category/national-economy", @@ -96,6 +172,7 @@ describe("parse-search-results", () => { "iri": "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4", "publicationStatus": "PUBLISHED", "subthemes": Array [], + "termsets": Array [], "themes": Array [ Object { "iri": "https://register.ld.admin.ch/opendataswiss/category/energy", @@ -116,62 +193,40 @@ describe("parse-search-results", () => { ], "title": "Gebäudeprogramm - CO2-Wirkungen je Massnahmenbereich", }, - Object { - "creator": Object { - "iri": "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe", - "label": "Swiss Federal Office of Energy SFOE", - }, - "datePublished": "2023-09-12", - "description": "Anzahl Gesuche mit Auszahlungen im jeweiligen Jahr, Gebäudeprogramm ab 2017 (nur direkte Massnahmen)", - "dimensions": Array [ - Object { - "iri": "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_anzahl_gesuche/region", - "label": "Kanton", - "termsets": Array [ - Object { - "iri": "https://ld.admin.ch/dimension/canton", - "label": "Cantons", - }, - ], - "timeUnit": "", - }, - ], - "iri": "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_anzahl_gesuche/15", - "publicationStatus": "PUBLISHED", - "subthemes": Array [], - "themes": Array [ - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/energy", - "label": "Energy", - }, - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/construction", - "label": "Construction and housing", - }, - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/population", - "label": "Population", - }, - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/statistical-basis", - "label": "Statistical basis", - }, - ], - "title": "Gebäudeprogramm - Anzahl unterstützter Gesuche", - }, ] `); }); - it("should build search cubes from CSV (shared dimension query)", async () => { + it("should build search cubes from CSV (shared dimension query, temporal)", async () => { const data = await parseCSV( path.join(__dirname, "./query-search-results-temporal.mock.csv") ); - // Build search cubes using the buildSearchCubes function const searchCubes = buildSearchCubes(data); expect(searchCubes.slice(0, 3)).toMatchInlineSnapshot(` Array [ + Object { + "creator": Object { + "iri": "https://register.ld.admin.ch/opendataswiss/org/elcom", + "label": "Eidgenössische Elektrizitätskommission ElCom", + }, + "datePublished": "2021-01-01", + "description": "Strompreise per Stromnetzbetreiber und Gemeinde in der Schweiz", + "dimensions": Array [ + Object { + "iri": "https://energy.ld.admin.ch/elcom/electricityprice/dimension/period", + "label": "", + "termsets": Array [], + "timeUnit": "http://www.w3.org/2006/time#unitYear", + }, + ], + "iri": "https://energy.ld.admin.ch/elcom/electricityprice", + "publicationStatus": "PUBLISHED", + "subthemes": Array [], + "termsets": Array [], + "themes": Array [], + "title": "Strompreis per Stromnetzbetreiber", + }, Object { "creator": Object { "iri": "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe", @@ -190,6 +245,7 @@ describe("parse-search-results", () => { "iri": "https://energy.ld.admin.ch/sfoe/OGD84GebTest/1", "publicationStatus": "PUBLISHED", "subthemes": Array [], + "termsets": Array [], "themes": Array [ Object { "iri": "https://register.ld.admin.ch/opendataswiss/category/energy", @@ -199,14 +255,6 @@ describe("parse-search-results", () => { "iri": "https://register.ld.admin.ch/opendataswiss/category/national-economy", "label": "Volkswirtschaft", }, - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/energy", - "label": "Energie", - }, - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/national-economy", - "label": "Volkswirtschaft", - }, ], "title": "GEB - Einmalvergütung für Photovoltaikanlagen", }, @@ -261,6 +309,7 @@ describe("parse-search-results", () => { "label": "Wirtschaft und Konsum", }, ], + "termsets": Array [], "themes": Array [ Object { "iri": "https://register.ld.admin.ch/opendataswiss/category/territory", @@ -281,39 +330,8 @@ describe("parse-search-results", () => { ], "title": "Statistik der Wasserkraftanlagen (WASTA)", }, - Object { - "creator": Object { - "iri": "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-umwelt-bafu", - "label": "Bundesamt für Umwelt BAFU", - }, - "datePublished": "2022-08-17", - "description": "Cf. IIR report Submission 2022.", - "dimensions": Array [ - Object { - "iri": "https://environment.ld.admin.ch/foen/BAFU_LuChem_EMIS_pollutants_KCA_all_years/year", - "label": "", - "termsets": Array [], - "timeUnit": "http://www.w3.org/2006/time#unitYear", - }, - ], - "iri": "https://environment.ld.admin.ch/foen/BAFU_LuChem_EMIS_pollutants_KCA_all_years/7", - "publicationStatus": "PUBLISHED", - "subthemes": Array [ - Object { - "iri": "https://register.ld.admin.ch/foen/theme/11", - "label": "Luft", - }, - ], - "themes": Array [ - Object { - "iri": "https://register.ld.admin.ch/opendataswiss/category/territory", - "label": "Raum und Umwelt", - }, - ], - "title": "Schlüsselkategorien für die wichtigsten Schadstoffe für das Einreichungsjahr 2022", - }, ] - `); +`); }); }); diff --git a/app/rdf/parse-search-results.ts b/app/rdf/parse-search-results.ts index 41f9b36ca..28f8f688e 100644 --- a/app/rdf/parse-search-results.ts +++ b/app/rdf/parse-search-results.ts @@ -4,6 +4,7 @@ import { Quad } from "rdf-js"; import { SearchCube } from "@/domain/data"; import { DataCubePublicationStatus } from "@/graphql/resolver-types"; import * as ns from "@/rdf/namespace"; +import { GROUP_SEPARATOR } from "@/rdf/query-utils"; const visualizePredicates = { hasDimension: ns.visualizeAdmin`hasDimension`.value, @@ -35,7 +36,10 @@ function buildSearchCubes( for (const iri of iriList) { const cubeQuads = bySubjectAndPredicate.get(iri); if (cubeQuads) { - const themeQuads = cubeQuads.get(ns.dcat.theme.value); + const themeQuads = cubeQuads.get("tag:/themeIris")?.[0]; + const themeIris = themeQuads?.object.value.split(GROUP_SEPARATOR); + const themeLabelQuads = cubeQuads.get("tag:/themeLabels")?.[0]; + const themeLabels = themeLabelQuads?.object.value.split(GROUP_SEPARATOR); const subthemesQuads = cubeQuads.get(ns.schema.about.value); const dimensions = cubeQuads.get(visualizePredicates.hasDimension); const creatorIri = cubeQuads.get(ns.schema.creator.value)?.[0]?.object @@ -43,6 +47,9 @@ function buildSearchCubes( const publicationStatus = cubeQuads.get( ns.schema.creativeWorkStatus.value )?.[0].object.value; + const termsetQuads = byPredicateAndObject + .get("https://cube.link/meta/isUsedIn") + ?.get(iri); const cubeSearchCube: SearchCube = { iri, @@ -67,7 +74,14 @@ function buildSearchCubes( } : null, themes: - themeQuads?.map((x) => { + themeIris?.map((iri, i) => { + return { + iri, + label: themeLabels?.[i] ?? "", + }; + }) ?? [], + subthemes: + subthemesQuads?.map((x) => { return { iri: x.object.value, label: @@ -76,13 +90,13 @@ function buildSearchCubes( ?.get(ns.schema.name.value)?.[0].object.value ?? "", }; }) ?? [], - subthemes: - subthemesQuads?.map((x) => { + termsets: + termsetQuads?.map((x) => { return { - iri: x.object.value, + iri: x.subject.value, label: bySubjectAndPredicate - .get(x.object.value) + .get(x.subject.value) ?.get(ns.schema.name.value)?.[0].object.value ?? "", }; }) ?? [], diff --git a/app/rdf/query-dimension-values.ts b/app/rdf/query-dimension-values.ts index 46f8383d8..852855f4f 100644 --- a/app/rdf/query-dimension-values.ts +++ b/app/rdf/query-dimension-values.ts @@ -18,10 +18,7 @@ import * as ns from "@/rdf/namespace"; import { parseDimensionDatatype } from "@/rdf/parse"; import { dimensionIsVersioned } from "@/rdf/queries"; import { executeWithCache } from "@/rdf/query-cache"; -import { - buildLocalizedSubQuery, - formatIriToQueryNode, -} from "@/rdf/query-utils"; +import { buildLocalizedSubQuery, iriToNode } from "@/rdf/query-utils"; /** * Formats a filter value into the right format, string literal @@ -30,7 +27,7 @@ import { */ const formatFilterValue = (value: string | number, dataType?: Term) => { if (!dataType) { - return formatIriToQueryNode(value as string); + return iriToNode(value as string); } else { return `"${value}"`; } @@ -547,7 +544,7 @@ export const loadMinMaxDimensionValues = async ({ cache: LRUCache | undefined; }) => { const query = SELECT`(MIN(?value) as ?minValue) (MAX(?value) as ?maxValue)` - .WHERE`${formatIriToQueryNode(datasetIri)} ${ns.cube.observationSet}/${ns.cube.observation} ?observation . + .WHERE`${iriToNode(datasetIri)} ${ns.cube.observationSet}/${ns.cube.observation} ?observation . ?observation <${dimensionIri}> ?value . FILTER ( (STRLEN(STR(?value)) > 0) && (STR(?value) != "NaN") )`; diff --git a/app/rdf/query-geo-shapes.ts b/app/rdf/query-geo-shapes.ts index 314f68abb..6d5eec893 100644 --- a/app/rdf/query-geo-shapes.ts +++ b/app/rdf/query-geo-shapes.ts @@ -3,7 +3,7 @@ import { NamedNode } from "rdf-js"; import { ParsingClient } from "sparql-http-client/ParsingClient"; import { MAX_BATCH_SIZE } from "@/graphql/context"; -import { formatIriToQueryNode } from "@/rdf/query-utils"; +import { iriToNode } from "@/rdf/query-utils"; export interface GeoShape { geometryIri: string; @@ -22,7 +22,7 @@ type GeoShapesLoaderProps = { export const createGeoShapesLoader = (props: GeoShapesLoaderProps) => { const { geoSparqlClient } = props; return async (geometryIris: readonly string[]): Promise => { - const geometryIriQueryNodes = geometryIris.map(formatIriToQueryNode); + const geometryIriQueryNodes = geometryIris.map(iriToNode); const wktStringsByGeometry = await getWktStringsByGeometry({ geometryIriQueryNodes, geoSparqlClient, diff --git a/app/rdf/query-possible-filters.ts b/app/rdf/query-possible-filters.ts index 1382db5b5..5d531b610 100644 --- a/app/rdf/query-possible-filters.ts +++ b/app/rdf/query-possible-filters.ts @@ -8,7 +8,7 @@ import { SingleFilters } from "@/config-types"; import { isMostRecentValue } from "@/domain/most-recent-value"; import * as ns from "@/rdf/namespace"; import { loadMaxDimensionValue } from "@/rdf/query-dimension-values"; -import { formatIriToQueryNode } from "@/rdf/query-utils"; +import { iriToNode } from "@/rdf/query-utils"; export const getPossibleFilters = async ( cubeIri: string, @@ -68,7 +68,7 @@ SELECT ?${DIMENSION_IRI} ?${VERSION} ?${NODE_KIND} WHERE { ?dimension sh:path ?${DIMENSION_IRI} . OPTIONAL { ?dimension schema:version ?${VERSION} . } OPTIONAL { ?dimension sh:nodeKind ?${NODE_KIND} . } - ${dimensionIris.length > 0 ? `FILTER(?${DIMENSION_IRI} IN (${dimensionIris.map(formatIriToQueryNode).join(", ")}))` : ""} + ${dimensionIris.length > 0 ? `FILTER(?${DIMENSION_IRI} IN (${dimensionIris.map(iriToNode).join(", ")}))` : ""} }`; const results = await sparqlClient.query.select(query, { operation: "postUrlencoded", @@ -140,7 +140,7 @@ const stringifyDimension = (i: number, isVersioned: boolean) => { }; const getQueryValue = (value: string, isLiteral: boolean) => { - return isLiteral ? `"${value}"` : formatIriToQueryNode(value); + return isLiteral ? `"${value}"` : iriToNode(value); }; type QueryFilter = { diff --git a/app/rdf/query-search-results-photovoltaikanlagen.mock.csv b/app/rdf/query-search-results-photovoltaikanlagen.mock.csv new file mode 100644 index 000000000..550863dc2 --- /dev/null +++ b/app/rdf/query-search-results-photovoltaikanlagen.mock.csv @@ -0,0 +1,17 @@ +"subject","predicate","object" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://www.w3.org/1999/02/22-rdf-syntax-ns#type","https://cube.link/Cube" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","https://cube.link/observationConstraint","https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10/shape/" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://purl.org/dc/terms/publisher","Pronovo AG" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/creativeWorkStatus","https://ld.admin.ch/vocabulary/CreativeWorkStatus/Published" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/creator","https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/datePublished","2022-08-16" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/description","Seit 2014 werden Photovoltaikanlagen mit einer Einmalvergütung (EIV) gefördert. Dabei wird abhängig von der Leistung, der Anlagenkategorie und dem Inbetriebnahmedatum ein einmaliger Beitrag an die Anlagenbetreiber ausbezahlt. Hier finden Sie pro Kanton und Auszahlungsjahr einen Überblick über die Anzahl geförderter EIV-Anlagen, die installierte Leistung in Kilowatt (kW) sowie den ausbezahlten EIV-Förderbeitrag. Die dargestellten Daten entsprechen nicht vollständig der offiziellen Statistik der erneuerbaren Energien durch das BFE. Da der Abbau der Wartelisten zeitverzögert stattfindet, können Abweichungen entstehen." +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/name","Einmalvergütung für Photovoltaikanlagen" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","http://schema.org/workExample","https://ld.admin.ch/application/visualize" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","tag:/themeIris","https://register.ld.admin.ch/opendataswiss/category/energy|||https://register.ld.admin.ch/opendataswiss/category/national-economy" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10","tag:/themeLabels","Energy|||National economy" +"https://ld.admin.ch/dimension/office","https://cube.link/meta/isUsedIn","https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10" +"https://ld.admin.ch/dimension/office","http://schema.org/name","Federal Offices" +"https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe","http://schema.org/name","Swiss Federal Office of Energy SFOE" +"https://ld.admin.ch/dimension/canton","https://cube.link/meta/isUsedIn","https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/10" +"https://ld.admin.ch/dimension/canton","http://schema.org/name","Cantons" \ No newline at end of file diff --git a/app/rdf/query-search-results-shared-dimensions.mock.csv b/app/rdf/query-search-results-shared-dimensions.mock.csv index 3bfd7df5a..1bd84a395 100644 --- a/app/rdf/query-search-results-shared-dimensions.mock.csv +++ b/app/rdf/query-search-results-shared-dimensions.mock.csv @@ -32,6 +32,8 @@ "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14","http://schema.org/name","Einmalvergütung für Photovoltaikanlagen" "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14","http://schema.org/workExample","https://ld.admin.ch/application/visualize" "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14","https://visualize.admin.ch/hasDimension","https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/Kanton" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14","tag:/themeIris","https://register.ld.admin.ch/opendataswiss/category/national-economy|||https://register.ld.admin.ch/opendataswiss/category/energy" +"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/14","tag:/themeLabels","National economy|||Energy" "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/Kanton","https://visualize.admin.ch/hasTermset","https://ld.admin.ch/dimension/canton" "https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/Kanton","http://schema.org/name","Kanton" "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe","http://schema.org/name","Swiss Federal Office of Energy SFOE" @@ -48,6 +50,8 @@ "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4","http://schema.org/name","Gebäudeprogramm - CO2-Wirkungen je Massnahmenbereich" "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4","http://schema.org/workExample","https://ld.admin.ch/application/visualize" "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4","https://visualize.admin.ch/hasDimension","https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/region" +"https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4","tag:/themeIris","https://register.ld.admin.ch/opendataswiss/category/energy|||https://register.ld.admin.ch/opendataswiss/category/construction|||https://register.ld.admin.ch/opendataswiss/category/statistical-basis|||https://register.ld.admin.ch/opendataswiss/category/population" +"https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/4","tag:/themeLabels","Energy|||Construction and housing|||Statistical basis|||Population" "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/region","https://visualize.admin.ch/hasTermset","https://ld.admin.ch/dimension/country" "https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_co2wirkung/region","http://schema.org/name","Region" "https://ld.admin.ch/dimension/country","http://schema.org/name","Countries" diff --git a/app/rdf/query-search-results-temporal.mock.csv b/app/rdf/query-search-results-temporal.mock.csv index 26d61f120..8e3795ca7 100644 --- a/app/rdf/query-search-results-temporal.mock.csv +++ b/app/rdf/query-search-results-temporal.mock.csv @@ -30,6 +30,8 @@ "https://energy.ld.admin.ch/sfoe/OGD84GebTest/1","http://schema.org/name","GEB - Einmalvergütung für Photovoltaikanlagen" "https://energy.ld.admin.ch/sfoe/OGD84GebTest/1","http://schema.org/workExample","https://ld.admin.ch/application/visualize" "https://energy.ld.admin.ch/sfoe/OGD84GebTest/1","https://visualize.admin.ch/hasDimension","https://energy.ld.admin.ch/sfoe/OGD84GebTest/Jahr" +"https://energy.ld.admin.ch/sfoe/OGD84GebTest/1","tag:/themeIris","https://register.ld.admin.ch/opendataswiss/category/energy|||https://register.ld.admin.ch/opendataswiss/category/national-economy" +"https://energy.ld.admin.ch/sfoe/OGD84GebTest/1","tag:/themeLabels","Energie|||Volkswirtschaft" "https://energy.ld.admin.ch/sfoe/OGD84GebTest/Jahr","https://visualize.admin.ch/hasTimeUnit","http://www.w3.org/2006/time#unitYear" "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-energie-bfe","http://schema.org/name","Bundesamt für Energie BFE" "https://register.ld.admin.ch/opendataswiss/category/energy","http://schema.org/name","Energie" @@ -53,6 +55,8 @@ "https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/7","http://schema.org/name","Statistik der Wasserkraftanlagen (WASTA)" "https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/7","http://schema.org/workExample","https://ld.admin.ch/application/visualize" "https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/7","https://visualize.admin.ch/hasDimension","https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/YearOfStatistic" +"https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/7","tag:/themeIris","https://register.ld.admin.ch/opendataswiss/category/territory|||https://register.ld.admin.ch/opendataswiss/category/energy|||https://register.ld.admin.ch/opendataswiss/category/geography|||https://register.ld.admin.ch/opendataswiss/category/culture" +"https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/7","tag:/themeLabels","Raum und Umwelt|||Energie|||Geographie|||Kultur, Medien, Informationsgesellschaft, Sport" "https://energy.ld.admin.ch/sfoe/bfe_ogd40_wasta/YearOfStatistic","https://visualize.admin.ch/hasTimeUnit","http://www.w3.org/2006/time#unitYear" "https://register.ld.admin.ch/opendataswiss/category/territory","http://schema.org/name","Raum und Umwelt" "https://register.ld.admin.ch/foen/theme/15","http://schema.org/inDefinedTermSet","https://register.ld.admin.ch/foen/theme" diff --git a/app/rdf/query-search.ts b/app/rdf/query-search.ts index e00e6d6cb..dc606fa76 100644 --- a/app/rdf/query-search.ts +++ b/app/rdf/query-search.ts @@ -13,15 +13,16 @@ import { buildSearchCubes } from "@/rdf/parse-search-results"; import { computeScores, highlight } from "@/rdf/query-search-score-utils"; import { buildLocalizedSubQuery, - formatIriToQueryNode, + GROUP_SEPARATOR, + iriToNode, makeVisualizeDatasetFilter, } from "@/rdf/query-utils"; -const makeInFilter = (name: string, values: string[]) => { +export const makeInFilter = (name: string, values: string[]) => { return ` ${ values.length > 0 - ? `FILTER (bound(?${name}) && ?${name} IN (${values.map(formatIriToQueryNode)}))` + ? `FILTER (bound(?${name}) && ?${name} IN (${values.map(iriToNode)}))` : "" }`; }; @@ -38,7 +39,7 @@ const fanOutExclusiveFilters = ( } const { exclusive = [], common = [] } = groupBy(filters, (f) => { - return f.type === SearchCubeFilterType.Termset || + return f.type === SearchCubeFilterType.DataCubeTermset || f.type === SearchCubeFilterType.TemporalDimension ? "exclusive" : "common"; @@ -67,12 +68,14 @@ export const searchCubes = async ({ locale: _locale, filters, includeDrafts, + fetchDimensionTermsets, sparqlClient, }: { query?: string | null; locale?: string | null; filters?: SearchCubeFilter[] | null; includeDrafts?: Boolean | null; + fetchDimensionTermsets?: boolean | null; sparqlClient: ParsingClient; }) => { const locale = _locale ?? defaultLocale; @@ -94,7 +97,8 @@ export const searchCubes = async ({ creatorValues, themeValues, includeDrafts, - query + query, + fetchDimensionTermsets ) ); @@ -145,28 +149,29 @@ const mkScoresQuery = ( creatorValues: string[], themeValues: string[], includeDrafts: Boolean | null | undefined, - query: string | null | undefined + query: string | null | undefined, + fetchDimensionTermsets: boolean | null | undefined ) => { return ` ${pragmas} # HOTFIX WRT Stardog v9.2.1 bug see https://control.vshn.net/tickets/sbar-1066 #pragma join.bind off + PREFIX cube: + PREFIX cubeMeta: + PREFIX dcat: + PREFIX dcterms: PREFIX meta: PREFIX rdf: PREFIX schema: PREFIX sh: - PREFIX cube: - PREFIX cubeMeta: - PREFIX dcterms: - PREFIX dcat: - PREFIX visualize: PREFIX time: + PREFIX visualize: CONSTRUCT { - ?iri a cube:Cube ; - cube:observationConstraint ?shape; - dcat:theme ?themeIri; + ?iri + a cube:Cube ; + cube:observationConstraint ?shape ; dcterms:publisher ?publisher ; schema:about ?subthemeIri; schema:creativeWorkStatus ?status ; @@ -175,19 +180,49 @@ const mkScoresQuery = ( schema:description ?description ; schema:name ?title ; schema:workExample ; - visualize:hasDimension ?dimensionIri. + visualize:hasDimension ?dimensionIri ; + ?themeIris ; + ?themeLabels . ?dimensionIri - visualize:hasTermset ?termsetIri ; visualize:hasTimeUnit ?unitType ; - schema:name ?dimensionLabel . - - ?termsetIri schema:name ?termsetLabel . + schema:name ?dimensionLabel ; + visualize:hasTermset ?dimensionTermsetIri . + + ?dimensionTermsetIri schema:name ?dimensionTermsetLabel . + + ?termsetIri + meta:isUsedIn ?iri ; + schema:name ?termsetLabel . + ?creatorIri schema:name ?creatorLabel . - ?themeIri schema:name ?themeLabel . - ?subthemeIri schema:inDefinedTermSet ?subthemeTermset ; - schema:name ?subthemeLabel . - } + + ?subthemeIri + schema:inDefinedTermSet ?subthemeTermset ; + schema:name ?subthemeLabel . + } WHERE { + SELECT + ?iri + ?shape + (GROUP_CONCAT(DISTINCT ?themeIri; SEPARATOR="${GROUP_SEPARATOR}") as ?themeIris) + (GROUP_CONCAT(DISTINCT ?themeLabel; SEPARATOR="${GROUP_SEPARATOR}") as ?themeLabels) + ?publisher + ?status + ?creatorIri + ?datePublished + ?description + ?title + ?dimensionIri + ?dimensionLabel + ?dimensionTermsetIri + ?dimensionTermsetLabel + ?unitType + ?termsetIri + ?termsetLabel + ?creatorLabel + ?subthemeIri + ?subthemeTermset + ?subthemeLabel WHERE { ?iri a cube:Cube . ${buildLocalizedSubQuery("iri", "schema:name", "title", { @@ -198,9 +233,9 @@ const mkScoresQuery = ( })} ${filters - ?.map((df) => { - if (df.type === SearchCubeFilterType.TemporalDimension) { - const value = df.value as TimeUnit; + ?.map((filter) => { + if (filter.type === SearchCubeFilterType.TemporalDimension) { + const value = filter.value as TimeUnit; const unitNode = unitsToNode.get(value); if (!unitNode) { throw new Error(`Invalid temporal unit used ${value}`); @@ -222,31 +257,45 @@ const mkScoresQuery = ( } )} `; - } else if (df.type === SearchCubeFilterType.Termset) { - const sharedDimensions = df.value.split(";"); - return ` - VALUES (?termsetIri) {${sharedDimensions.map((sd) => `(<${sd}>)`).join(" ")}} - ?iri cube:observationConstraint/sh:property ?dimension . - ?dimension - sh:path ?dimensionIri ; - a cube:KeyDimension ; - sh:in/rdf:first ?value . - ?value schema:inDefinedTermSet ?termsetIri . - ${buildLocalizedSubQuery( - "dimension", - "schema:name", - "dimensionLabel", - { locale } - )} - ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })}`; + } else if (filter.type === SearchCubeFilterType.DataCubeTermset) { + if (fetchDimensionTermsets) { + const sharedDimensions = filter.value.split(";"); + return ` + VALUES (?dimensionTermsetIri) {${sharedDimensions.map((sd) => `(${iriToNode(sd)})`).join(" ")}} + ?iri cube:observationConstraint/sh:property ?dimension . + ${buildLocalizedSubQuery("dimension", "schema:name", "dimensionLabel", { locale })} + ?dimension + sh:path ?dimensionIri ; + a cube:KeyDimension ; + sh:in/rdf:first ?value . + ?value schema:inDefinedTermSet ?dimensionTermsetIri . + ${buildLocalizedSubQuery("dimensionTermsetIri", "schema:name", "dimensionTermsetLabel", { locale })} + `; + } else { + const termsets = filter.value.split(";"); + return ` + VALUES (?termsetIri) {${termsets.map((termset) => `(${iriToNode(termset)})`).join(" ")}} + ?termsetIri meta:isUsedIn ?iri . + ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })}`; + } } }) .filter(truthy) .join("\n")} + ${ + !filters?.find((f) => f.type === SearchCubeFilterType.DataCubeTermset) + ? ` + OPTIONAL { + ?termsetIri meta:isUsedIn ?iri . + ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })} + }` + : "" + } + # Publisher, creator status, datePublished - OPTIONAL { ?iri dcterms:publisher ?publisher . } ?iri schema:creativeWorkStatus ?status . + OPTIONAL { ?iri dcterms:publisher ?publisher . } OPTIONAL { ?iri schema:datePublished ?datePublished . } ${creatorValues.length ? makeInFilter("creatorIri", creatorValues) : ""} @@ -274,21 +323,22 @@ const mkScoresQuery = ( })} } } + ${ themeValues.length ? ` - VALUES ?theme { ${themeValues.map(formatIriToQueryNode).join(" ")} } + VALUES ?theme { ${themeValues.map(iriToNode).join(" ")} } ?iri dcat:theme ?theme . ` : "" } # Add more subtheme termsets here when they are available - ${ - creatorValues.includes( - "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-umwelt-bafu" - ) - ? ` + ${ + creatorValues.includes( + "https://register.ld.admin.ch/opendataswiss/org/bundesamt-fur-umwelt-bafu" + ) + ? ` OPTIONAL { ?iri schema:about ?subthemeIri . VALUES (?subthemeGraph ?subthemeTermset) { ( ) } @@ -304,13 +354,13 @@ const mkScoresQuery = ( )} } ` - : "" - } + : "" + } ${makeVisualizeDatasetFilter({ includeDrafts: !!includeDrafts, cubeIriVar: "?iri", - }).toString()} + })} ${ query @@ -355,6 +405,26 @@ const mkScoresQuery = ( )` : "" } - - }`; + } + GROUP BY + ?iri + ?shape + ?publisher + ?status + ?creatorIri + ?datePublished + ?description + ?title + ?dimensionIri + ?dimensionLabel + ?dimensionTermsetIri + ?dimensionTermsetLabel + ?unitType + ?termsetIri + ?termsetLabel + ?creatorLabel + ?subthemeIri + ?subthemeTermset + ?subthemeLabel + }`; }; diff --git a/app/rdf/query-termsets.ts b/app/rdf/query-termsets.ts index 1798c5478..78f3dd4c9 100644 --- a/app/rdf/query-termsets.ts +++ b/app/rdf/query-termsets.ts @@ -2,42 +2,7 @@ import groupBy from "lodash/groupBy"; import ParsingClient from "sparql-http-client/ParsingClient"; import { ComponentTermsets, Termset } from "@/domain/data"; -import { - buildLocalizedSubQuery, - makeVisualizeDatasetFilter, -} from "@/rdf/query-utils"; - -export const queryAllTermsets = async (options: { - locale: string; - sparqlClient: ParsingClient; - includeDrafts?: boolean; -}): Promise<{ termset: Termset; count: number }[]> => { - const { sparqlClient, locale, includeDrafts } = options; - const qs = await sparqlClient.query.select( - `PREFIX cube: -PREFIX meta: -PREFIX schema: - -SELECT DISTINCT (COUNT(DISTINCT ?cubeIri) as ?count) ?termsetIri ?termsetLabel WHERE { - ?termsetIri meta:isUsedIn ?cubeIri . - ${makeVisualizeDatasetFilter({ - includeDrafts: !!includeDrafts, - cubeIriVar: "?cubeIri", - })} - ${buildLocalizedSubQuery("termsetIri", "schema:name", "termsetLabel", { locale })} -} GROUP BY ?termsetIri ?termsetLabel`, - { operation: "postUrlencoded" } - ); - - return qs.map((result) => ({ - count: +result.count.value, - termset: { - __typename: "Termset", - iri: result.termsetIri.value, - label: result.termsetLabel.value, - }, - })); -}; +import { buildLocalizedSubQuery } from "@/rdf/query-utils"; export const getCubeTermsets = async ( iri: string, diff --git a/app/rdf/query-utils.ts b/app/rdf/query-utils.ts index 85e3a28ee..5be348e4b 100644 --- a/app/rdf/query-utils.ts +++ b/app/rdf/query-utils.ts @@ -2,7 +2,7 @@ import { locales } from "@/locales/locales"; export const GROUP_SEPARATOR = "|||"; -export const formatIriToQueryNode = (iri: string) => { +export const iriToNode = (iri: string) => { return `<${iri}>`; };