diff --git a/app/browser/dataset-preview.tsx b/app/browser/dataset-preview.tsx
index 4432352009..3387a2fdd3 100644
--- a/app/browser/dataset-preview.tsx
+++ b/app/browser/dataset-preview.tsx
@@ -7,13 +7,15 @@ import * as React from "react";
import { DataSetPreviewTable } from "@/browse/datatable";
import { useFootnotesStyles } from "@/components/chart-footnotes";
-import { DataDownloadMenu, RunSparqlQuery } from "@/components/data-download";
+import { DataDownloadMenu } from "@/components/data-download";
import Flex from "@/components/flex";
import { HintRed, Loading, LoadingDataError } from "@/components/hint";
import { DataSource } from "@/config-types";
import { sourceToLabel } from "@/domain/datasource";
-import { useDataCubesComponentsQuery } from "@/graphql/hooks";
-import { useDataCubePreviewQuery } from "@/graphql/query-hooks";
+import {
+ useDataCubeMetadataQuery,
+ useDataCubePreviewQuery,
+} from "@/graphql/query-hooks";
import { DataCubePublicationStatus } from "@/graphql/resolver-types";
import { useLocale } from "@/locales/use-locale";
@@ -93,55 +95,38 @@ export const DataSetPreview = ({
}) => {
const footnotesClasses = useFootnotesStyles({ useMarginTop: false });
const locale = useLocale();
- const cubeFilters = [{ iri: dataSetIri }];
+ const variables = {
+ sourceType: dataSource.type,
+ sourceUrl: dataSource.url,
+ locale,
+ cubeFilter: { iri: dataSetIri },
+ };
+ const [{ data: metadata, fetching: fetchingMetadata, error: metadataError }] =
+ useDataCubeMetadataQuery({ variables });
const [
{ data: previewData, fetching: fetchingPreview, error: previewError },
- ] = useDataCubePreviewQuery({
- variables: {
- iri: dataSetIri,
- sourceType: dataSource.type,
- sourceUrl: dataSource.url,
- locale,
- },
- });
- const [
- {
- data: componentsData,
- fetching: fetchingComponents,
- error: componentsError,
- },
- ] = useDataCubesComponentsQuery({
- variables: {
- sourceType: dataSource.type,
- sourceUrl: dataSource.url,
- locale,
- cubeFilters,
- },
- });
+ ] = useDataCubePreviewQuery({ variables });
const classes = useStyles({
- descriptionPresent: !!previewData?.dataCubeByIri?.description,
+ descriptionPresent: !!metadata?.dataCubeMetadata.description,
});
React.useEffect(() => {
window.scrollTo({ top: 0 });
}, []);
- if (fetchingPreview || fetchingComponents) {
+ if (fetchingMetadata || fetchingPreview) {
return (
);
- } else if (
- previewData?.dataCubeByIri &&
- componentsData?.dataCubesComponents
- ) {
- const { dataCubeByIri } = previewData;
- const { dataCubesComponents } = componentsData;
+ } else if (metadata?.dataCubeMetadata && previewData?.dataCubePreview) {
+ const { dataCubeMetadata } = metadata;
+ const { dataCubePreview } = previewData;
return (
- {dataCubeByIri.publicationStatus ===
+ {dataCubeMetadata.publicationStatus ===
DataCubePublicationStatus.Draft && (
@@ -156,15 +141,15 @@ export const DataSetPreview = ({
- {dataCubeByIri.title} - visualize.admin.ch
+ {dataCubeMetadata.title} - visualize.admin.ch
- {dataCubeByIri.title}
+ {dataCubeMetadata.title}
- {dataCubeByIri.description && (
+ {dataCubeMetadata.description && (
- {dataCubeByIri.description}
+ {dataCubeMetadata.description}
)}
- {dataCubeByIri.observations.sparqlEditorUrl && (
+ {/* {dataCubeByIri.observations.sparqlEditorUrl && (
- )}
+ )} */}
);
diff --git a/app/domain/data.ts b/app/domain/data.ts
index a4d214200a..1fb6b0f0f3 100644
--- a/app/domain/data.ts
+++ b/app/domain/data.ts
@@ -40,21 +40,6 @@ export type HierarchyValue = {
children?: HierarchyValue[];
};
-export type Observation = Record;
-
-export type DataCubeObservations = {
- data: Observation[];
- sparqlEditorUrl: string;
-};
-
-export type DataCubesObservations = {
- data: Observation[];
- sparqlEditorUrls: {
- cubeIri: string;
- url: string;
- }[];
-};
-
export type DataCubeComponents = {
dimensions: Dimension[];
measures: Measure[];
@@ -82,6 +67,27 @@ export type DataCubeMetadata = {
workExamples?: string[];
};
+export type Observation = Record;
+
+export type DataCubeObservations = {
+ data: Observation[];
+ sparqlEditorUrl: string;
+};
+
+export type DataCubesObservations = {
+ data: Observation[];
+ sparqlEditorUrls: {
+ cubeIri: string;
+ url: string;
+ }[];
+};
+
+export type DataCubePreview = {
+ dimensions: Dimension[];
+ measures: Measure[];
+ observations: Observation[];
+};
+
export type Component = Dimension | Measure;
export type BaseComponent = {
diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql
index 4cc9c08f2f..a278e2e526 100644
--- a/app/graphql/queries/data-cubes.graphql
+++ b/app/graphql/queries/data-cubes.graphql
@@ -40,6 +40,20 @@ query DataCubeObservations(
)
}
+query DataCubePreview(
+ $sourceType: String!
+ $sourceUrl: String!
+ $locale: String!
+ $cubeFilter: DataCubePreviewFilter!
+) {
+ dataCubePreview(
+ sourceType: $sourceType
+ sourceUrl: $sourceUrl
+ locale: $locale
+ cubeFilter: $cubeFilter
+ )
+}
+
query SearchCubes(
$sourceType: String!
$sourceUrl: String!
@@ -64,38 +78,6 @@ query SearchCubes(
}
}
-query DataCubePreview(
- $iri: String!
- $sourceType: String!
- $sourceUrl: String!
- $locale: String!
- $latest: Boolean
- $disableValuesLoad: Boolean = true
-) {
- dataCubeByIri(
- iri: $iri
- sourceType: $sourceType
- sourceUrl: $sourceUrl
- locale: $locale
- latest: $latest
- disableValuesLoad: $disableValuesLoad
- ) {
- iri
- title
- description
- publicationStatus
- observations(
- sourceType: $sourceType
- sourceUrl: $sourceUrl
- preview: true
- limit: 10
- ) {
- data
- sparqlEditorUrl
- }
- }
-}
-
query GeoCoordinatesByDimensionIri(
$dataCubeIri: String!
$dimensionIri: String!
diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts
index 436d9a7067..1acad2aa53 100644
--- a/app/graphql/query-hooks.ts
+++ b/app/graphql/query-hooks.ts
@@ -1,6 +1,7 @@
import { DataCubeComponents } from '../domain/data';
import { DataCubeMetadata } from '../domain/data';
import { DataCubeObservations } from '../domain/data';
+import { DataCubePreview } from '../domain/data';
import { DimensionValue } from '../domain/data';
import { Filters } from '../configurator';
import { HierarchyValue } from '../domain/data';
@@ -24,6 +25,7 @@ export type Scalars = {
DataCubeComponents: DataCubeComponents;
DataCubeMetadata: DataCubeMetadata;
DataCubeObservations: DataCubeObservations;
+ DataCubePreview: DataCubePreview;
DimensionValue: DimensionValue;
FilterValue: any;
Filters: Filters;
@@ -111,7 +113,6 @@ export type DataCubeMetadataFilter = {
export type DataCubeObservationFilter = {
iri: Scalars['String'];
latest?: Maybe;
- preview?: Maybe;
filters?: Maybe;
componentIris?: Maybe>;
joinBy?: Maybe;
@@ -124,6 +125,12 @@ export type DataCubeOrganization = {
label?: Maybe;
};
+
+export type DataCubePreviewFilter = {
+ iri: Scalars['String'];
+ latest?: Maybe;
+};
+
export enum DataCubePublicationStatus {
Draft = 'DRAFT',
Published = 'PUBLISHED'
@@ -384,6 +391,7 @@ export type Query = {
dataCubeComponents: Scalars['DataCubeComponents'];
dataCubeMetadata: Scalars['DataCubeMetadata'];
dataCubeObservations: Scalars['DataCubeObservations'];
+ dataCubePreview: Scalars['DataCubePreview'];
dataCubeByIri?: Maybe;
possibleFilters: Array;
searchCubes: Array;
@@ -414,6 +422,14 @@ export type QueryDataCubeObservationsArgs = {
};
+export type QueryDataCubePreviewArgs = {
+ sourceType: Scalars['String'];
+ sourceUrl: Scalars['String'];
+ locale: Scalars['String'];
+ cubeFilter: DataCubePreviewFilter;
+};
+
+
export type QueryDataCubeByIriArgs = {
sourceType: Scalars['String'];
sourceUrl: Scalars['String'];
@@ -613,30 +629,28 @@ export type DataCubeObservationsQueryVariables = Exact<{
export type DataCubeObservationsQuery = { __typename: 'Query', dataCubeObservations: DataCubeObservations };
-export type SearchCubesQueryVariables = Exact<{
+export type DataCubePreviewQueryVariables = Exact<{
sourceType: Scalars['String'];
sourceUrl: Scalars['String'];
locale: Scalars['String'];
- query?: Maybe;
- order?: Maybe;
- includeDrafts?: Maybe;
- filters?: Maybe | SearchCubeFilter>;
+ cubeFilter: DataCubePreviewFilter;
}>;
-export type SearchCubesQuery = { __typename: 'Query', searchCubes: Array<{ __typename: 'SearchCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, cube: SearchCube }> };
+export type DataCubePreviewQuery = { __typename: 'Query', dataCubePreview: DataCubePreview };
-export type DataCubePreviewQueryVariables = Exact<{
- iri: Scalars['String'];
+export type SearchCubesQueryVariables = Exact<{
sourceType: Scalars['String'];
sourceUrl: Scalars['String'];
locale: Scalars['String'];
- latest?: Maybe;
- disableValuesLoad?: Maybe;
+ query?: Maybe;
+ order?: Maybe;
+ includeDrafts?: Maybe;
+ filters?: Maybe | SearchCubeFilter>;
}>;
-export type DataCubePreviewQuery = { __typename: 'Query', dataCubeByIri?: Maybe<{ __typename: 'DataCube', iri: string, title: string, description?: Maybe, publicationStatus: DataCubePublicationStatus, observations: { __typename: 'ObservationsQuery', data: Array, sparqlEditorUrl?: Maybe } }> };
+export type SearchCubesQuery = { __typename: 'Query', searchCubes: Array<{ __typename: 'SearchCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, cube: SearchCube }> };
export type GeoCoordinatesByDimensionIriQueryVariables = Exact<{
dataCubeIri: Scalars['String'];
@@ -715,6 +729,20 @@ export const DataCubeObservationsDocument = gql`
export function useDataCubeObservationsQuery(options: Omit, 'query'> = {}) {
return Urql.useQuery({ query: DataCubeObservationsDocument, ...options });
};
+export const DataCubePreviewDocument = gql`
+ query DataCubePreview($sourceType: String!, $sourceUrl: String!, $locale: String!, $cubeFilter: DataCubePreviewFilter!) {
+ dataCubePreview(
+ sourceType: $sourceType
+ sourceUrl: $sourceUrl
+ locale: $locale
+ cubeFilter: $cubeFilter
+ )
+}
+ `;
+
+export function useDataCubePreviewQuery(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(
@@ -736,36 +764,6 @@ export const SearchCubesDocument = gql`
export function useSearchCubesQuery(options: Omit, 'query'> = {}) {
return Urql.useQuery({ query: SearchCubesDocument, ...options });
};
-export const DataCubePreviewDocument = gql`
- query DataCubePreview($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean, $disableValuesLoad: Boolean = true) {
- dataCubeByIri(
- iri: $iri
- sourceType: $sourceType
- sourceUrl: $sourceUrl
- locale: $locale
- latest: $latest
- disableValuesLoad: $disableValuesLoad
- ) {
- iri
- title
- description
- publicationStatus
- observations(
- sourceType: $sourceType
- sourceUrl: $sourceUrl
- preview: true
- limit: 10
- ) {
- data
- sparqlEditorUrl
- }
- }
-}
- `;
-
-export function useDataCubePreviewQuery(options: Omit, 'query'> = {}) {
- return Urql.useQuery({ query: DataCubePreviewDocument, ...options });
-};
export const GeoCoordinatesByDimensionIriDocument = gql`
query GeoCoordinatesByDimensionIri($dataCubeIri: String!, $dimensionIri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean) {
dataCubeByIri(
diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts
index 1d11d17da9..d62abb6e28 100644
--- a/app/graphql/resolver-types.ts
+++ b/app/graphql/resolver-types.ts
@@ -1,6 +1,7 @@
import { DataCubeComponents } from '../domain/data';
import { DataCubeMetadata } from '../domain/data';
import { DataCubeObservations } from '../domain/data';
+import { DataCubePreview } from '../domain/data';
import { DimensionValue } from '../domain/data';
import { Filters } from '../configurator';
import { HierarchyValue } from '../domain/data';
@@ -25,6 +26,7 @@ export type Scalars = {
DataCubeComponents: DataCubeComponents;
DataCubeMetadata: DataCubeMetadata;
DataCubeObservations: DataCubeObservations;
+ DataCubePreview: DataCubePreview;
DimensionValue: DimensionValue;
FilterValue: any;
Filters: Filters;
@@ -112,7 +114,6 @@ export type DataCubeMetadataFilter = {
export type DataCubeObservationFilter = {
iri: Scalars['String'];
latest?: Maybe;
- preview?: Maybe;
filters?: Maybe;
componentIris?: Maybe>;
joinBy?: Maybe;
@@ -125,6 +126,12 @@ export type DataCubeOrganization = {
label?: Maybe;
};
+
+export type DataCubePreviewFilter = {
+ iri: Scalars['String'];
+ latest?: Maybe;
+};
+
export enum DataCubePublicationStatus {
Draft = 'DRAFT',
Published = 'PUBLISHED'
@@ -385,6 +392,7 @@ export type Query = {
dataCubeComponents: Scalars['DataCubeComponents'];
dataCubeMetadata: Scalars['DataCubeMetadata'];
dataCubeObservations: Scalars['DataCubeObservations'];
+ dataCubePreview: Scalars['DataCubePreview'];
dataCubeByIri?: Maybe;
possibleFilters: Array;
searchCubes: Array;
@@ -415,6 +423,14 @@ export type QueryDataCubeObservationsArgs = {
};
+export type QueryDataCubePreviewArgs = {
+ sourceType: Scalars['String'];
+ sourceUrl: Scalars['String'];
+ locale: Scalars['String'];
+ cubeFilter: DataCubePreviewFilter;
+};
+
+
export type QueryDataCubeByIriArgs = {
sourceType: Scalars['String'];
sourceUrl: Scalars['String'];
@@ -661,6 +677,8 @@ export type ResolversTypes = ResolversObject<{
DataCubeObservationFilter: DataCubeObservationFilter;
DataCubeObservations: ResolverTypeWrapper;
DataCubeOrganization: ResolverTypeWrapper;
+ DataCubePreview: ResolverTypeWrapper;
+ DataCubePreviewFilter: DataCubePreviewFilter;
DataCubePublicationStatus: DataCubePublicationStatus;
DataCubeTheme: ResolverTypeWrapper;
Dimension: ResolverTypeWrapper;
@@ -710,6 +728,8 @@ export type ResolversParentTypes = ResolversObject<{
DataCubeObservationFilter: DataCubeObservationFilter;
DataCubeObservations: Scalars['DataCubeObservations'];
DataCubeOrganization: DataCubeOrganization;
+ DataCubePreview: Scalars['DataCubePreview'];
+ DataCubePreviewFilter: DataCubePreviewFilter;
DataCubeTheme: DataCubeTheme;
Dimension: ResolvedDimension;
DimensionValue: Scalars['DimensionValue'];
@@ -784,6 +804,10 @@ export type DataCubeOrganizationResolvers;
}>;
+export interface DataCubePreviewScalarConfig extends GraphQLScalarTypeConfig {
+ name: 'DataCubePreview';
+}
+
export type DataCubeThemeResolvers = ResolversObject<{
iri?: Resolver;
label?: Resolver, ParentType, ContextType>;
@@ -961,6 +985,7 @@ export type QueryResolvers>;
dataCubeMetadata?: Resolver>;
dataCubeObservations?: Resolver>;
+ dataCubePreview?: Resolver>;
dataCubeByIri?: Resolver, ParentType, ContextType, RequireFields>;
possibleFilters?: Resolver, ParentType, ContextType, RequireFields>;
searchCubes?: Resolver, ParentType, ContextType, RequireFields>;
@@ -1052,6 +1077,7 @@ export type Resolvers = ResolversObject<{
DataCubeMetadata?: GraphQLScalarType;
DataCubeObservations?: GraphQLScalarType;
DataCubeOrganization?: DataCubeOrganizationResolvers;
+ DataCubePreview?: GraphQLScalarType;
DataCubeTheme?: DataCubeThemeResolvers;
Dimension?: DimensionResolvers;
DimensionValue?: GraphQLScalarType;
diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts
index ca20e7f939..7456b87ce4 100644
--- a/app/graphql/resolvers/index.ts
+++ b/app/graphql/resolvers/index.ts
@@ -9,6 +9,7 @@ import {
DataCubeResolvers,
QueryResolvers,
Resolvers,
+ ScaleType,
} from "@/graphql/resolver-types";
import * as RDF from "@/graphql/resolvers/rdf";
import * as SQL from "@/graphql/resolvers/sql";
@@ -37,6 +38,10 @@ export const Query: QueryResolvers = {
const source = getSource(args.sourceType);
return await source.dataCubeObservations(parent, args, context, info);
},
+ dataCubePreview: async (parent, args, context, info) => {
+ const source = getSource(args.sourceType);
+ return await source.dataCubePreview(parent, args, context, info);
+ },
dataCubeByIri: async (parent, args, context, info) => {
const source = getSource(args.sourceType);
return await source.dataCubeByIri(parent, args, context, info);
@@ -83,10 +88,10 @@ const DataCube: DataCubeResolvers = {
};
export const resolveDimensionType = (
- component: ResolvedDimension
+ dataKind: ResolvedDimension["data"]["dataKind"] | undefined,
+ scaleType: ScaleType | undefined,
+ related: ResolvedDimension["data"]["related"]
): DimensionType => {
- const { dataKind, scaleType, related } = component.data;
-
if (related.some((d) => d.type === "StandardError")) {
return "StandardErrorDimension";
}
@@ -113,16 +118,14 @@ export const resolveDimensionType = (
};
export const resolveMeasureType = (
- component: ResolvedDimension
+ scaleType: ScaleType | undefined
): MeasureType => {
- const { scaleType } = component.data;
-
return scaleType === "Ordinal" ? "OrdinalMeasure" : "NumericalMeasure";
};
const mkDimensionResolvers = (_: string): Resolvers["Dimension"] => ({
- __resolveType(dimension) {
- return resolveDimensionType(dimension);
+ __resolveType({ data: { dataKind, scaleType, related } }) {
+ return resolveDimensionType(dataKind, scaleType, related);
},
iri: ({ data: { iri } }) => iri,
label: ({ data: { name } }) => name,
@@ -201,8 +204,8 @@ export const resolvers: Resolvers = {
},
},
Dimension: {
- __resolveType(dimension) {
- return resolveDimensionType(dimension);
+ __resolveType({ data: { dataKind, scaleType, related } }) {
+ return resolveDimensionType(dataKind, scaleType, related);
},
},
NominalDimension: {
@@ -273,7 +276,7 @@ export const resolvers: Resolvers = {
},
Measure: {
__resolveType(dimension) {
- return resolveMeasureType(dimension);
+ return resolveMeasureType(dimension.data.scaleType);
},
},
NumericalMeasure: {
diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts
index d59c72a5a4..69cd3f3a0d 100644
--- a/app/graphql/resolvers/rdf.ts
+++ b/app/graphql/resolvers/rdf.ts
@@ -30,6 +30,7 @@ import {
getLatestCube,
} from "@/rdf/queries";
import { getCubeMetadata } from "@/rdf/query-cube-metadata";
+import { getCubePreview } from "@/rdf/query-cube-preview";
import { unversionObservation } from "@/rdf/query-dimension-values";
import { queryHierarchy } from "@/rdf/query-hierarchies";
import { SearchResult, searchCubes as _searchCubes } from "@/rdf/query-search";
@@ -209,7 +210,7 @@ export const dataCubeComponents: NonNullable<
if (data.isMeasureDimension) {
const result: Measure = {
- __typename: resolveMeasureType(component),
+ __typename: resolveMeasureType(component.data.scaleType),
isCurrency: data.isCurrency,
isDecimal: data.isDecimal,
currencyExponent: data.currencyExponent,
@@ -219,7 +220,12 @@ export const dataCubeComponents: NonNullable<
measures.push(result);
} else {
- const dimensionType = resolveDimensionType(component);
+ const { dataKind, scaleType, related } = component.data;
+ const dimensionType = resolveDimensionType(
+ dataKind,
+ scaleType,
+ related
+ );
const hierarchy = true // TODO: make this configurable
? await queryHierarchy(
component,
@@ -322,6 +328,18 @@ export const dataCubeObservations: NonNullable<
};
};
+export const dataCubePreview: NonNullable =
+ async (_, { locale, cubeFilter }, { setup }, info) => {
+ const { sparqlClient } = await setup(info);
+ const { iri, latest } = cubeFilter;
+
+ return await getCubePreview(iri, {
+ locale,
+ latest: !!latest,
+ sparqlClient,
+ });
+ };
+
export const dataCubeDimensions: NonNullable =
async ({ cube, locale }, { componentIris }, { setup }, info) => {
const { sparqlClient, cache } = await setup(info);
diff --git a/app/graphql/resolvers/sql.ts b/app/graphql/resolvers/sql.ts
index 266b60ebc4..3316469ece 100644
--- a/app/graphql/resolvers/sql.ts
+++ b/app/graphql/resolvers/sql.ts
@@ -324,3 +324,12 @@ export const dataCubeObservations: NonNullable<
sparqlEditorUrl: "",
};
};
+
+export const dataCubePreview: NonNullable =
+ async () => {
+ return {
+ dimensions: [],
+ measures: [],
+ observations: [],
+ };
+ };
diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql
index e8948abb9b..eea01b77a3 100644
--- a/app/graphql/schema.graphql
+++ b/app/graphql/schema.graphql
@@ -354,15 +354,20 @@ input DataCubeMetadataFilter {
input DataCubeObservationFilter {
iri: String!
latest: Boolean
- preview: Boolean
filters: Filters
componentIris: [String!]
joinBy: String
}
+input DataCubePreviewFilter {
+ iri: String!
+ latest: Boolean
+}
+
scalar DataCubeComponents
scalar DataCubeMetadata
scalar DataCubeObservations
+scalar DataCubePreview
# The "Query" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each.
@@ -385,6 +390,12 @@ type Query {
locale: String!
cubeFilter: DataCubeObservationFilter!
): DataCubeObservations!
+ dataCubePreview(
+ sourceType: String!
+ sourceUrl: String!
+ locale: String!
+ cubeFilter: DataCubePreviewFilter!
+ ): DataCubePreview!
dataCubeByIri(
sourceType: String!
sourceUrl: String!
diff --git a/app/rdf/parse.ts b/app/rdf/parse.ts
index 96d67c56dc..6b62c41bc4 100644
--- a/app/rdf/parse.ts
+++ b/app/rdf/parse.ts
@@ -95,7 +95,7 @@ export const parseCube = ({
};
};
-const timeUnits = new Map([
+export const timeUnits = new Map([
[ns.time.unitYear.value, TimeUnit.Year],
[ns.time.unitMonth.value, TimeUnit.Month],
[ns.time.unitWeek.value, TimeUnit.Week],
@@ -105,16 +105,16 @@ const timeUnits = new Map([
[ns.time.unitSecond.value, TimeUnit.Second],
]);
-const timeFormats = new Map([
+export const timeFormats = new Map([
[ns.xsd.gYear.value, "%Y"],
[ns.xsd.gYearMonth.value, "%Y-%m"],
[ns.xsd.date.value, "%Y-%m-%d"],
[ns.xsd.dateTime.value, "%Y-%m-%dT%H:%M:%S"],
]);
-export const getScaleType = (dim: CubeDimension): ScaleType | undefined => {
- const scaleTypeTerm = dim.out(ns.qudt.scaleType).term;
-
+export const getScaleType = (
+ scaleTypeTerm: Term | undefined
+): ScaleType | undefined => {
return scaleTypeTerm?.equals(ns.qudt.NominalScale)
? ScaleType.Nominal
: scaleTypeTerm?.equals(ns.qudt.OrdinalScale)
@@ -126,7 +126,7 @@ export const getScaleType = (dim: CubeDimension): ScaleType | undefined => {
: undefined;
};
-const getDataKind = (term: Term | undefined) => {
+export const getDataKind = (term: Term | undefined) => {
return term?.equals(ns.time.GeneralDateTimeDescription)
? "Time"
: term?.equals(ns.schema.GeoCoordinates)
@@ -285,7 +285,7 @@ export const parseCubeDimension = ({
dataKind: getDataKind(dataKindTerm),
timeUnit: timeUnits.get(timeUnitTerm?.value ?? ""),
timeFormat: timeFormats.get(dataType?.value ?? ""),
- scaleType: getScaleType(dim),
+ scaleType: getScaleType(dim.out(ns.qudt.scaleType).term),
},
};
};
diff --git a/app/rdf/queries.ts b/app/rdf/queries.ts
index 4b132c2f47..dbac475c09 100644
--- a/app/rdf/queries.ts
+++ b/app/rdf/queries.ts
@@ -400,7 +400,7 @@ export const getCubeDimensionValuesWithMetadata = async ({
const result: DimensionValue[] = [];
if (namedNodes.length > 0) {
- const scaleType = getScaleType(dimension);
+ const scaleType = getScaleType(dimension.out(ns.qudt.scaleType).term);
const [labels, descriptions, literals, unversioned] = await Promise.all([
loadResourceLabels({
ids: namedNodes,
diff --git a/app/rdf/query-cube-preview.ts b/app/rdf/query-cube-preview.ts
new file mode 100644
index 0000000000..83c88a485b
--- /dev/null
+++ b/app/rdf/query-cube-preview.ts
@@ -0,0 +1,321 @@
+import uniqBy from "lodash/uniqBy";
+import ParsingClient from "sparql-http-client/ParsingClient";
+
+import {
+ BaseComponent,
+ BaseDimension,
+ DataCubePreview,
+ Dimension,
+ Measure,
+ TemporalDimension,
+} from "@/domain/data";
+import { resolveDimensionType, resolveMeasureType } from "@/graphql/resolvers";
+
+import * as ns from "./namespace";
+import { getDataKind, getScaleType, timeFormats, timeUnits } from "./parse";
+import { buildLocalizedSubQuery } from "./query-utils";
+
+export const getCubePreview = async (
+ iri: string,
+ options: {
+ locale: string;
+ latest: Boolean;
+ sparqlClient: ParsingClient;
+ }
+): Promise => {
+ const { sparqlClient, locale } = options;
+ const quads = await sparqlClient.query.construct(
+ `PREFIX cube:
+ PREFIX meta:
+ PREFIX qudt:
+ PREFIX rdf:
+ PREFIX schema:
+ PREFIX sh:
+ PREFIX time:
+ PREFIX xsd:
+
+ CONSTRUCT {
+ ?cube schema:version ?cubeVersion .
+ ?cube cube:observationSet ?observationSet .
+ ?observationSet cube:observation ?observation .
+
+ ?dimension sh:path ?dimensionIri .
+ ?dimension sh:datatype ?dimensionDataType .
+ ?dimension rdf:type ?dimensionType .
+ ?dimension qudt:scaleType ?dimensionScaleType .
+ ?dimension qudt:unit ?dimensionUnit .
+ ?dimensionUnit schema:name ?dimensionUnitLabel .
+ ?dimensionUnit qudt:CurrencyUnit ?dimensionUnitIsCurrency .
+ ?dimensionUnit qudt:currencyExponent ?dimensionUnitCurrencyExponent .
+ ?dimension sh:order ?dimensionOrder .
+ ?dimension meta:dataKind ?dimensionDataKind .
+ ?dimensionDataKind rdf:type ?dimensionDataKindType .
+ ?dimensionDataKind time:unitType ?dimensionTimeUnitType .
+ ?dimension schema:name ?dimensionLabel .
+ ?dimension schema:description ?dimensionDescription .
+
+ ?observation ?dimensionIri ?observationValue .
+ ?observation ?dimensionIri ?observationValueLabel .
+ } WHERE {
+ VALUES ?cube { <${iri}> }
+ FILTER(EXISTS { ?cube a cube:Cube . }) {}
+ UNION {
+ ?cube cube:observationConstraint/sh:property ?dimension .
+ ?dimension sh:path ?dimensionIri .
+ OPTIONAL { ?dimension sh:datatype ?dimensionDataType . }
+ OPTIONAL { ?dimension rdf:type ?dimensionType . }
+ OPTIONAL { ?dimension qudt:scaleType ?dimensionScaleType . }
+ OPTIONAL {
+ ?dimension qudt:unit ?_dimensionUnit .
+ OPTIONAL { ?_dimensionUnit rdfs:label ?_dimensionUnitRdfsLabel . }
+ OPTIONAL { ?_dimensionUnit qudt:symbol ?_dimensionUnitSymbol . }
+ OPTIONAL { ?_dimensionUnit qudt:ucumCode ?_dimensionUnitUcumCode . }
+ OPTIONAL { ?_dimensionUnit qudt:expression ?_dimensionUnitExpression . }
+ OPTIONAL { ?_dimensionUnit ?_isCurrencyUnit qudt:CurrencyUnit . }
+ OPTIONAL { ?_dimensionUnit qudt:currencyExponent ?_dimensionUnitCurrencyExponent . }
+ BIND(STR(COALESCE(STR(?_dimensionUnitSymbol), STR(?_dimensionUnitUcumCode), STR(?_dimensionUnitExpression), STR(?_dimensionUnitRdfsLabel), "?")) AS ?_dimensionUnitLabel)
+ FILTER (LANG(?_dimensionUnitRdfsLabel) = "en")
+ }
+ OPTIONAL {
+ ?dimension qudt:hasUnit ?_dimensionHasUnit .
+ OPTIONAL { ?_dimensionHasUnit rdfs:label ?_dimensionHasUnitRdfsLabel . }
+ OPTIONAL { ?_dimensionHasUnit qudt:symbol ?_dimensionHasUnitSymbol . }
+ OPTIONAL { ?_dimensionHasUnit qudt:ucumCode ?_dimensionHasUnitUcumCode . }
+ OPTIONAL { ?_dimensionHasUnit qudt:expression ?_dimensionHasUnitExpression . }
+ OPTIONAL { ?_dimensionHasUnit ?_isCurrencyHasUnit qudt:CurrencyUnit . }
+ OPTIONAL { ?_dimensionHasUnit qudt:currencyExponent ?_dimensionHasUnitCurrencyExponent . }
+ BIND(STR(COALESCE(STR(?_dimensionHasUnitSymbol), STR(?_dimensionHasUnitUcumCode), STR(?_dimensionHasUnitExpression), STR(?_dimensionHasUnitRdfsLabel), "?")) AS ?_dimensionHasUnitLabel)
+ FILTER (LANG(?_dimensionHasUnitRdfsLabel) = "en")
+ }
+ BIND(COALESCE(?_dimensionUnit, ?_dimensionHasUnit) as ?dimensionUnit)
+ BIND(COALESCE(?_dimensionUnitLabel, ?_dimensionHasUnitLabel) as ?dimensionUnitLabel)
+ BIND(COALESCE(?_dimensionUnitIsCurrency, ?_dimensionHasUnitIsCurrency) as ?dimensionUnitIsCurrency)
+ BIND(COALESCE(?_dimensionUnitCurrencyExponent, ?_dimensionHasUnitCurrencyExponent) as ?dimensionUnitCurrencyExponent)
+ OPTIONAL { ?dimension sh:order ?dimensionOrder . }
+ OPTIONAL {
+ ?dimension meta:dataKind ?dimensionDataKind .
+ ?dimensionDataKind rdf:type ?dimensionDataKindType .
+ }
+ OPTIONAL {
+ ?dimension meta:dataKind ?dimensionDataKind .
+ ?dimensionDataKind time:unitType ?dimensionTimeUnitType .
+ }
+ ${buildLocalizedSubQuery("dimension", "schema:name", "dimensionLabel", {
+ locale,
+ })}
+ ${buildLocalizedSubQuery(
+ "dimension",
+ "schema:description",
+ "dimensionDescription",
+ { locale }
+ )}
+ } UNION {
+ ?cube cube:observationConstraint/sh:property/sh:path ?dimensionIri .
+
+ { SELECT * WHERE {
+ ?cube cube:observationSet ?observationSet .
+ ?observationSet cube:observation ?observation .
+ FILTER(NOT EXISTS { ?cube cube:observationConstraint/sh:property/sh:datatype cube:Undefined . } && NOT EXISTS { ?observation ?p ""^^cube:Undefined . })
+ } LIMIT 10 }
+
+ { SELECT * WHERE {
+ { SELECT * WHERE {
+ ?cube cube:observationSet/cube:observation ?observation .
+ FILTER(NOT EXISTS { ?cube cube:observationConstraint/sh:property/sh:datatype cube:Undefined . } && NOT EXISTS { ?observation ?p ""^^cube:Undefined . })
+ } LIMIT 10 }
+
+ ?observation ?dimensionIri ?observationValue .
+ OPTIONAL { ?observationValue schema:name ?observationValueLabel_de . FILTER(LANG(?observationValueLabel_de) = "de") }
+ OPTIONAL { ?observationValue schema:name ?observationValueLabel_fr . FILTER(LANG(?observationValueLabel_fr) = "fr") }
+ OPTIONAL { ?observationValue schema:name ?observationValueLabel_it . FILTER(LANG(?observationValueLabel_it) = "it") }
+ OPTIONAL { ?observationValue schema:name ?observationValueLabel_en . FILTER(LANG(?observationValueLabel_en) = "en") }
+ OPTIONAL { ?observationValue schema:name ?observationValueLabel_ . FILTER(LANG(?observationValueLabel_) = "") }
+ BIND(COALESCE(?observationValueLabel_de, ?observationValueLabel_fr, ?observationValueLabel_it, ?observationValueLabel_en, ?observationValueLabel_) AS ?observationValueLabel)
+ }}
+ }
+ }`,
+ { operation: "postUrlencoded" }
+ );
+
+ if (quads.length === 0) {
+ throw new Error(`No cube found for ${iri}!`);
+ }
+
+ const dimensionsQuads = quads.filter(
+ (quad) =>
+ quad.predicate.equals(ns.sh.path) &&
+ !quad.object.equals(ns.cube.observedBy) &&
+ !quad.object.equals(ns.rdf.type)
+ );
+ const dimensions: Dimension[] = [];
+ const measures: Measure[] = [];
+ uniqBy(dimensionsQuads, (quad) => quad.object.value).map((quad) => {
+ const dimensionQuads = quads.filter((q) => q.subject.equals(quad.subject));
+ const dimensionIri = quad.object.value;
+ const labelQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.schema.name)
+ );
+ const descriptionQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.schema.description)
+ );
+ const orderQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.sh.order)
+ );
+ const typeQuads = dimensionQuads.filter((q) =>
+ q.predicate.equals(ns.rdf.type)
+ );
+ const scaleTypeQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.qudt.scaleType)
+ );
+ const scaleType = getScaleType(scaleTypeQuad?.object);
+ const dataTypeQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.sh.datatype)
+ );
+ const unitQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.qudt.unit)
+ );
+ const unitLabelQuad = quads.find((q) => q.subject.equals(unitQuad?.object));
+ const dataKindQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.cube("meta/dataKind"))
+ );
+ const dataKindTypeQuad = quads.find(
+ (q) =>
+ q.subject.equals(dataKindQuad?.object) &&
+ q.predicate.equals(ns.rdf.type)
+ );
+ const timeUnitTypeQuad = quads.find(
+ (q) =>
+ q.subject.equals(dataKindQuad?.object) &&
+ q.predicate.equals(ns.time.unitType)
+ );
+ const isCurrencyQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.qudt.CurrencyUnit)
+ );
+ const currencyExponentQuad = dimensionQuads.find((q) =>
+ q.predicate.equals(ns.qudt.currencyExponent)
+ );
+ const isKeyDimension = typeQuads.some((q) =>
+ q.object.equals(ns.cube.KeyDimension)
+ );
+ const isMeasureDimension = typeQuads.some((q) =>
+ q.object.equals(ns.cube.MeasureDimension)
+ );
+
+ const baseComponent: BaseComponent = {
+ cubeIri: iri,
+ iri: dimensionIri,
+ label: labelQuad?.object.value ?? "",
+ description: descriptionQuad?.object.value,
+ scaleType,
+ unit: unitLabelQuad?.object.value,
+ order:
+ orderQuad?.object.termType === "Literal"
+ ? parseInt(orderQuad.object.value)
+ : undefined,
+ isNumerical: false,
+ isKeyDimension,
+ values: [],
+ };
+
+ if (isMeasureDimension) {
+ const isDecimal = dataTypeQuad?.object.equals(ns.xsd.decimal) ?? false;
+ const result: Measure = {
+ ...baseComponent,
+ __typename: resolveMeasureType(scaleType),
+ isCurrency: isCurrencyQuad ? true : false,
+ isDecimal,
+ currencyExponent: currencyExponentQuad
+ ? parseInt(currencyExponentQuad.object.value)
+ : undefined,
+ resolution:
+ dataTypeQuad?.object.equals(ns.xsd.int) ||
+ dataTypeQuad?.object.equals(ns.xsd.integer)
+ ? 0
+ : undefined,
+ isNumerical:
+ dataTypeQuad?.object.equals(ns.xsd.int) ||
+ dataTypeQuad?.object.equals(ns.xsd.integer) ||
+ isDecimal ||
+ dataTypeQuad?.object.equals(ns.xsd.float) ||
+ dataTypeQuad?.object.equals(ns.xsd.double) ||
+ false,
+ };
+
+ measures.push(result);
+ } else {
+ const dimensionType = resolveDimensionType(
+ getDataKind(dataKindTypeQuad?.object),
+ scaleType,
+ []
+ );
+ const baseDimension: BaseDimension = baseComponent;
+
+ switch (dimensionType) {
+ case "TemporalDimension": {
+ const timeUnit = timeUnits.get(timeUnitTypeQuad?.object.value ?? "");
+ const timeFormat = timeFormats.get(dataTypeQuad?.object.value ?? "");
+
+ if (!timeFormat || !timeUnit) {
+ throw new Error(
+ `Temporal dimension ${dimensionIri} is missing timeFormat or timeUnit!`
+ );
+ }
+
+ const dimension: TemporalDimension = {
+ ...baseDimension,
+ __typename: dimensionType,
+ timeFormat,
+ timeUnit,
+ };
+ dimensions.push(dimension);
+ break;
+ }
+ default: {
+ const dimension: Exclude = {
+ ...baseDimension,
+ __typename: dimensionType,
+ };
+ dimensions.push(dimension);
+ }
+ }
+ }
+ });
+
+ const observationQuads = quads.filter((quad) =>
+ quad.predicate.equals(ns.cube.observation)
+ );
+ const observations = uniqBy(
+ observationQuads,
+ (quad) => quad.object.value
+ ).map((quad) => {
+ const dimensionValueQuads = dimensionsQuads.map((dimensionQuad) => {
+ return quads.find(
+ (q) =>
+ q.subject.equals(quad.object) &&
+ q.predicate.equals(dimensionQuad.object)
+ );
+ });
+
+ return dimensionValueQuads.reduce((acc, quad) => {
+ if (!quad) return acc;
+
+ if (!acc[quad.predicate.value]) {
+ const rootObservationId = quads.filter((q) =>
+ q.object.equals(quad.object)
+ );
+
+ acc[quad.predicate.value] =
+ quads.find(
+ (q) =>
+ q.subject.equals(rootObservationId[0].subject) &&
+ q.predicate.equals(quad.predicate) &&
+ q.object.termType === "Literal"
+ )?.object.value ?? quad.object.value;
+ }
+
+ return acc;
+ }, {} as Record);
+ });
+
+ return { dimensions, measures, observations };
+};
diff --git a/app/scripts/cube.ts b/app/scripts/cube.ts
index 2728734395..e897529b09 100644
--- a/app/scripts/cube.ts
+++ b/app/scripts/cube.ts
@@ -130,11 +130,10 @@ const previewCube = async ({
.query(
DataCubePreviewDocument,
{
- iri,
sourceType,
sourceUrl,
locale,
- latest,
+ cubeFilter: { iri, latest },
}
)
.toPromise();
@@ -143,7 +142,7 @@ const previewCube = async ({
throw new Error(res.error.message);
}
- report(res.data?.dataCubeByIri?.observations);
+ report(res.data?.dataCubePreview?.observations);
};
const main = async () => {
diff --git a/codegen.yml b/codegen.yml
index 186faf866b..af331dbd8d 100644
--- a/codegen.yml
+++ b/codegen.yml
@@ -25,6 +25,7 @@ generates:
DataCubeComponents: "../domain/data#DataCubeComponents"
DataCubeMetadata: "../domain/data#DataCubeMetadata"
DataCubeObservations: "../domain/data#DataCubeObservations"
+ DataCubePreview: "../domain/data#DataCubePreview"
app/graphql/resolver-types.ts:
plugins:
- "typescript"
@@ -45,6 +46,7 @@ generates:
DataCubeComponents: "../domain/data#DataCubeComponents"
DataCubeMetadata: "../domain/data#DataCubeMetadata"
DataCubeObservations: "../domain/data#DataCubeObservations"
+ DataCubePreview: "../domain/data#DataCubePreview"
mappers:
DataCube: "./shared-types#ResolvedDataCube"
ObservationsQuery: "./shared-types#ResolvedObservationsQuery"