From d1d888b9bf1d622666daed5f01e0427cae0d968d Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 29 Aug 2025 12:59:14 +0200 Subject: [PATCH] WIP: Paginated table --- app/charts/shared/chart-props.tsx | 14 + app/charts/table/chart-table.tsx | 8 +- app/charts/table/table-content.tsx | 50 +- .../table/table-paginated-data-wrapper.tsx | 342 +++++++ app/charts/table/table-state.tsx | 1 + app/charts/table/table.tsx | 186 +++- app/graphql/hooks.ts | 168 ++++ app/graphql/queries/data-cubes.graphql | 24 + app/graphql/query-hooks.ts | 849 ++++++++---------- app/graphql/resolver-types.ts | 800 +++++++---------- app/graphql/resolvers/index.ts | 4 + app/graphql/resolvers/rdf.ts | 74 ++ app/graphql/resolvers/sql.ts | 19 + app/graphql/schema.graphql | 33 + app/rdf/queries.ts | 191 +++- 15 files changed, 1799 insertions(+), 964 deletions(-) create mode 100644 app/charts/table/table-paginated-data-wrapper.tsx diff --git a/app/charts/shared/chart-props.tsx b/app/charts/shared/chart-props.tsx index 0e5bccaaa1..e57a8c600b 100644 --- a/app/charts/shared/chart-props.tsx +++ b/app/charts/shared/chart-props.tsx @@ -7,6 +7,19 @@ export type DimensionsById = Record; export type MeasuresById = Record; +export type PaginationControls = { + pageIndex: number; + pageSize: number; + canNextPage: boolean; + canPreviousPage: boolean; + totalCount: number; + nextPage: () => void; + previousPage: () => void; + gotoPage: (pageIndex: number) => void; + setPageSize: (pageSize: number) => void; + setSortBy: (sortBy: Array<{ id: string; desc: boolean }>) => void; +}; + export type BaseChartProps = { observations: Observation[]; dimensions: Dimension[]; @@ -14,6 +27,7 @@ export type BaseChartProps = { measures: Measure[]; measuresById: MeasuresById; embedParams?: EmbedQueryParams; + pagination?: PaginationControls; }; export type ChartProps = BaseChartProps & { diff --git a/app/charts/table/chart-table.tsx b/app/charts/table/chart-table.tsx index 1a6dbb85b5..76e70717ea 100644 --- a/app/charts/table/chart-table.tsx +++ b/app/charts/table/chart-table.tsx @@ -1,9 +1,9 @@ import { memo } from "react"; -import { ChartDataWrapper } from "@/charts/chart-data-wrapper"; import { BrushTime, shouldShowBrush } from "@/charts/shared/brush"; import { ChartContainer } from "@/charts/shared/containers"; import { Table, TABLE_TIME_RANGE_HEIGHT } from "@/charts/table/table"; +import { TablePaginatedDataWrapper } from "@/charts/table/table-paginated-data-wrapper"; import { TableChart } from "@/charts/table/table-state"; import { TableConfig } from "@/configurator"; import { hasChartConfigs, useConfiguratorState } from "@/configurator"; @@ -16,7 +16,7 @@ export const ChartTableVisualization = ( const { observationQueryFilters } = props; return ( - ) { - const { chartConfig } = props; + const { chartConfig, pagination } = props; const { interactiveFiltersConfig } = chartConfig; const [{ dashboardFilters }] = useConfiguratorState(hasChartConfigs); const showTimeBrush = shouldShowBrush( @@ -47,7 +47,7 @@ const ChartTable = memo(function ChartTable(props: ChartProps) { )} - +
); diff --git a/app/charts/table/table-content.tsx b/app/charts/table/table-content.tsx index cb21d67059..dbb72c250e 100644 --- a/app/charts/table/table-content.tsx +++ b/app/charts/table/table-content.tsx @@ -16,6 +16,8 @@ type TableContentProps = { tableColumnsMeta: Record; customSortCount: number; totalColumnsWidth: number; + handleSortClick?: (columnId: string) => void; + manualSortBy?: Array<{ id: string; desc: boolean }>; }; const TableContentContext = createContext( @@ -27,6 +29,8 @@ export const TableContentProvider = ({ tableColumnsMeta, customSortCount, totalColumnsWidth, + handleSortClick, + manualSortBy, children, }: TableContentProps & { children: ReactNode }) => { const value = useMemo(() => { @@ -35,8 +39,17 @@ export const TableContentProvider = ({ tableColumnsMeta, customSortCount, totalColumnsWidth, + handleSortClick, + manualSortBy, }; - }, [headerGroups, tableColumnsMeta, customSortCount, totalColumnsWidth]); + }, [ + headerGroups, + tableColumnsMeta, + customSortCount, + totalColumnsWidth, + handleSortClick, + manualSortBy, + ]); return ( @@ -75,8 +88,14 @@ export const TableContent = ({ children }: { children: ReactNode }) => { throw Error("Please wrap TableContent in TableContentProvider"); } - const { headerGroups, tableColumnsMeta, customSortCount, totalColumnsWidth } = - ctx; + const { + headerGroups, + tableColumnsMeta, + customSortCount, + totalColumnsWidth, + handleSortClick, + manualSortBy, + } = ctx; return ( <> @@ -89,9 +108,26 @@ export const TableContent = ({ children }: { children: ReactNode }) => { {headerGroup.headers.map((column) => { const { dim, columnComponentType } = tableColumnsMeta[column.id]; - // We assume that the customSortCount items are at the beginning of the sorted array, so any item with a lower index must be a custom sorted one + + // For manual sorting (server-side), check manualSortBy state + const manualSort = manualSortBy?.find( + (s) => s.id === column.id + ); + const isManualSorted = !!manualSort; + const manualSortDirection = manualSort?.desc ? "desc" : "asc"; + + // For react-table sorting (client-side), use existing logic const isCustomSorted = column.sortedIndex < customSortCount; + const isActive = handleSortClick + ? isManualSorted + : isCustomSorted; + const direction = handleSortClick + ? manualSortDirection + : column.isSortedDesc + ? "desc" + : "asc"; + return ( // eslint-disable-next-line react/jsx-key { ? classes.headerGroupMeasure : undefined )} - {...column.getHeaderProps(column.getSortByToggleProps())} > handleSortClick?.(column.id)} > diff --git a/app/charts/table/table-paginated-data-wrapper.tsx b/app/charts/table/table-paginated-data-wrapper.tsx new file mode 100644 index 0000000000..b62dae2efc --- /dev/null +++ b/app/charts/table/table-paginated-data-wrapper.tsx @@ -0,0 +1,342 @@ +import { makeStyles } from "@mui/styles"; +import { AnimatePresence } from "framer-motion"; +import keyBy from "lodash/keyBy"; +import { + ComponentProps, + createElement, + ElementType, + useEffect, + useMemo, + useState, +} from "react"; + +import { A11yTable } from "@/charts/shared/a11y-table"; +import { useLoadingState } from "@/charts/shared/chart-loading-state"; +import { ChartProps } from "@/charts/shared/chart-props"; +import { EmbedQueryParams } from "@/components/embed-params"; +import { Flex } from "@/components/flex"; +import { + Loading, + LoadingDataError, + LoadingOverlay, + NoDataHint, +} from "@/components/hint"; +import { ChartConfig, DataSource } from "@/configurator"; +import { + useDataCubesComponentsQuery, + useDataCubesMetadataQuery, + useDataCubesObservationsPaginatedQuery, +} from "@/graphql/hooks"; +import { DataCubeObservationFilter } from "@/graphql/query-hooks"; +import { useLocale } from "@/locales/use-locale"; + +type ElementProps = RE extends ElementType ? P : never; + +const useStyles = makeStyles(() => ({ + dataWrapper: { + position: "relative", + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + }, +})); + +export type PaginationState = { + pageIndex: number; + pageSize: number; + sortBy?: Array<{ id: string; desc: boolean }>; +}; + +/** + * Responsible for fetching paginated data for the table chart. + * - Provides paginated observations, dimensions and measures to the chart Component + * - Handles loading & error state + * - Manages pagination state + */ +const TablePaginatedDataWrapperInner = < + TChartConfig extends ChartConfig, + TOtherProps, + TChartComponent extends ElementType, +>({ + chartConfig, + LoadingOverlayComponent = LoadingOverlay, + Component, + ComponentProps, + componentIds, + dataSource, + embedParams, + observationQueryFilters, + fetching: fetchingProp = false, + error: propError, +}: { + chartConfig: TChartConfig; + LoadingOverlayComponent?: ElementType; + Component: TChartComponent; + ComponentProps?: Omit< + ElementProps, + keyof ChartProps + >; + componentIds?: string[]; + dataSource: DataSource; + embedParams?: EmbedQueryParams; + observationQueryFilters: DataCubeObservationFilter[]; + fetching?: boolean; + error?: Error; +}) => { + const chartLoadingState = useLoadingState(); + const classes = useStyles(); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const locale = useLocale(); + const commonQueryVariables = { + sourceType: dataSource.type, + sourceUrl: dataSource.url, + locale, + }; + + const [metadataQuery] = useDataCubesMetadataQuery({ + variables: { + ...commonQueryVariables, + cubeFilters: chartConfig.cubes.map((cube) => ({ iri: cube.iri })), + }, + keepPreviousData: true, + }); + + const [componentsQuery] = useDataCubesComponentsQuery({ + chartConfig, + variables: { + ...commonQueryVariables, + cubeFilters: chartConfig.cubes.map((cube) => ({ + iri: cube.iri, + componentIds, + joinBy: cube.joinBy, + loadValues: true, + })), + }, + keepPreviousData: true, + }); + + const paginatedObservationQueryFilters = useMemo( + () => + observationQueryFilters.map((filter) => { + // Combine configured sorting with dynamic sorting + const configSorting = (chartConfig as any).sorting?.map((sort: any) => ({ + componentId: sort.componentId, + order: sort.sortingOrder === "desc" ? ("DESC" as const) : ("ASC" as const), + })) || []; + + // Convert dynamic sorting to server format + const dynamicSorting = pagination.sortBy?.map((sort) => { + return { + componentId: sort.id, + order: sort.desc ? ("DESC" as const) : ("ASC" as const), + }; + }) || []; + + // Dynamic sorting takes precedence over config sorting + const orderBy = dynamicSorting.length > 0 ? dynamicSorting : configSorting; + + return { + ...filter, + limit: pagination.pageSize, + offset: pagination.pageIndex * pagination.pageSize, + orderBy, + }; + }), + [observationQueryFilters, pagination.pageIndex, pagination.pageSize, pagination.sortBy, chartConfig] + ); + + const [observationsPaginatedQuery] = useDataCubesObservationsPaginatedQuery({ + chartConfig, + variables: { + ...commonQueryVariables, + cubeFilters: paginatedObservationQueryFilters, + }, + keepPreviousData: true, + }); + + const { + data: metadataData, + fetching: fetchingMetadata, + error: metadataError, + } = metadataQuery; + const { + data: componentsData, + fetching: fetchingComponents, + error: componentsError, + } = componentsQuery; + const { + data: observationsPaginatedData, + fetching: fetchingObservations, + error: observationsError, + } = observationsPaginatedQuery; + + const metadata = metadataData?.dataCubesMetadata; + const observations = + observationsPaginatedData?.dataCubesObservationsPaginated?.data?.data; + const paginationInfo = + observationsPaginatedData?.dataCubesObservationsPaginated?.pagination; + const dimensions = componentsData?.dataCubesComponents.dimensions; + const measures = componentsData?.dataCubesComponents.measures; + + const fetching = + fetchingProp || + fetchingMetadata || + fetchingComponents || + fetchingObservations; + const error = + propError || metadataError || componentsError || observationsError; + + useEffect(() => { + chartLoadingState.set("data", fetching); + }, [chartLoadingState, fetching]); + + const { dimensionsById, measuresById } = useMemo(() => { + return { + dimensionsById: keyBy(dimensions ?? [], (d) => d.id), + measuresById: keyBy(measures ?? [], (d) => d.id), + }; + }, [dimensions, measures]); + + const canNextPage = paginationInfo?.hasNextPage ?? false; + const canPreviousPage = paginationInfo?.hasPreviousPage ?? false; + const totalCount = paginationInfo?.totalCount ?? 0; + + const nextPage = () => { + if (canNextPage) { + setPagination((prev) => ({ + ...prev, + pageIndex: prev.pageIndex + 1, + })); + } + }; + + const previousPage = () => { + if (canPreviousPage) { + setPagination((prev) => ({ + ...prev, + pageIndex: prev.pageIndex - 1, + })); + } + }; + + const gotoPage = (pageIndex: number) => { + setPagination((prev) => ({ + ...prev, + pageIndex, + })); + }; + + const setPageSize = (pageSize: number) => { + setPagination((prev) => ({ + ...prev, + pageSize, + pageIndex: 0, + })); + }; + + const setSortBy = (sortBy: Array<{ id: string; desc: boolean }>) => { + setPagination((prev) => ({ + ...prev, + sortBy, + pageIndex: 0, // Reset to first page when sorting changes + })); + }; + + if (error) { + return ( + + {metadataError && } + {componentsError && } + {observationsError && } + {propError && } + + ); + } else if ( + fetching && + (!metadata || !dimensions || !measures || !observations) + ) { + return ; + } else if (metadata && dimensions && measures && observations) { + const title = metadata.map((d) => d.title).join(", "); + + return ( +
+ {observations.length > 0 && ( + + )} + + {createElement(Component, { + observations, + dimensions, + dimensionsById, + measures, + measuresById, + chartConfig, + embedParams, + pagination: { + pageIndex: pagination.pageIndex, + pageSize: pagination.pageSize, + canNextPage, + canPreviousPage, + totalCount, + nextPage, + previousPage, + gotoPage, + setPageSize, + setSortBy, + }, + ...ComponentProps, + } as ChartProps & TOtherProps)} + + + {chartLoadingState.loading ? ( + + ) : observations.length === 0 ? ( + + ) : null} + +
+ ); + } else { + return null; + } +}; + +/** + * Makes sure the TablePaginatedDataWrapper is re-rendered when the cube iris and/or joinBy change. + * This ensures that data hooks do not keep stale data. + */ +export const TablePaginatedDataWrapper = ( + props: ComponentProps +) => { + const key = useMemo( + () => + props.chartConfig.cubes + .map((c) => `${c.iri}${c.joinBy ? `:${c.joinBy}` : ""}`) + .join(" / "), + [props.chartConfig.cubes] + ); + return ; +}; + +const Error = ({ message }: { message: string }) => { + return ( + + + + ); +}; diff --git a/app/charts/table/table-state.tsx b/app/charts/table/table-state.tsx index 7bc7c1322c..50ed99b2e7 100644 --- a/app/charts/table/table-state.tsx +++ b/app/charts/table/table-state.tsx @@ -127,6 +127,7 @@ const useTableState = ( const { chartConfig, dimensions, measures } = chartProps; const { getX } = variables; const { chartData, allData, timeRangeData } = data; + console.log({ chartData }); const { fields, settings, sorting } = chartConfig; const formatNumber = useFormatNumber(); diff --git a/app/charts/table/table.tsx b/app/charts/table/table.tsx index d4b43957f7..639f9a904b 100644 --- a/app/charts/table/table.tsx +++ b/app/charts/table/table.tsx @@ -1,18 +1,20 @@ import { t, Trans } from "@lingui/macro"; -import { Box, Theme, Typography } from "@mui/material"; +import { + Box, + Button, + MenuItem, + Select, + Theme, + Typography, +} from "@mui/material"; import { makeStyles } from "@mui/styles"; import FlexSearch from "flexsearch"; -import { forwardRef, useCallback, useMemo, useState } from "react"; -import { - useExpanded, - useFlexLayout, - useGroupBy, - useSortBy, - useTable, -} from "react-table"; +import { forwardRef, useCallback, useEffect, useMemo, useState } from "react"; +import { useExpanded, useFlexLayout, useGroupBy, useTable } from "react-table"; import AutoSizer from "react-virtualized-auto-sizer"; import { FixedSizeList, VariableSizeList } from "react-window"; +import { PaginationControls } from "@/charts/shared/chart-props"; import { useChartState } from "@/charts/shared/chart-state"; import { CellDesktop } from "@/charts/table/cell-desktop"; import { DDContent } from "@/charts/table/cell-mobile"; @@ -89,7 +91,7 @@ export const getTableUIElementsOffset = ({ ); }; -export const Table = () => { +export const Table = ({ pagination }: { pagination?: PaginationControls }) => { const { bounds, rowHeight, @@ -129,13 +131,90 @@ export const Table = () => { }, [tableColumnsMeta, chartData]); const filteredData = useMemo(() => { + // For server-side sorting (pagination), don't apply client-side filtering + // as it would mess up the server-sorted order + if (pagination) { + return chartData; + } + + // For client-side sorting, apply search filtering const result = searchTerm === "" ? chartData : searchIndex.search({ query: `${searchTerm}` }); return result as Observation[]; - }, [chartData, searchTerm, searchIndex]); + }, [chartData, searchTerm, searchIndex, pagination]); + + // Manual sort state for server-side sorting + const [manualSortBy, setManualSortBy] = useState< + Array<{ id: string; desc: boolean }> + >([]); + + // Handle manual sort icon clicks for server-side sorting + const handleSortClick = useCallback( + (columnId: string) => { + if (!pagination) return; // Only for server-side sorting + + setManualSortBy((prevSort) => { + const existingSort = prevSort.find((s) => s.id === columnId); + let newSort: Array<{ id: string; desc: boolean }>; + + if (!existingSort) { + // Add new sort ascending + newSort = [{ id: columnId, desc: false }]; + } else if (!existingSort.desc) { + // Change to descending + newSort = [{ id: columnId, desc: true }]; + } else { + // Remove sort + newSort = []; + } + + return newSort; + }); + }, + [pagination] + ); + + // Send manual sort changes to server + useEffect(() => { + if (pagination && manualSortBy.length > 0) { + const sortByWithOriginalIds = manualSortBy.map((sort) => { + const originalComponentId = + Object.values(tableColumnsMeta).find( + (meta: any) => meta.slugifiedId === sort.id + )?.id || sort.id; + return { + ...sort, + id: originalComponentId, + }; + }); + pagination.setSortBy(sortByWithOriginalIds); + } + }, [manualSortBy, pagination, tableColumnsMeta]); + + // Table configuration - completely disable sorting for server-side + const tableConfig = useMemo(() => { + const config = { + columns: pagination + ? tableColumns.map((col) => ({ + ...col, + disableSortBy: true, // Disable react-table sorting completely + })) + : tableColumns, + data: filteredData, + autoResetExpanded: false, + initialState: { + groupBy: groupingIds, + hiddenColumns: hiddenIds, + }, + }; + + // No sorting logic at all + + return config; + }, [pagination, tableColumns, filteredData, groupingIds, hiddenIds]); // Table Instance const { @@ -146,35 +225,15 @@ export const Table = () => { totalColumnsWidth, prepareRow, visibleColumns, - state: tableState, } = useTable( - { - columns: tableColumns, - data: filteredData, - autoResetExpanded: false, - useControlledState: (state) => { - return useMemo( - () => ({ - ...state, - sortBy: [...state.sortBy, ...sortingIds], - groupBy: groupingIds, - hiddenColumns: hiddenIds, - }), - // eslint does not detect correctly the dependencies here due to the - // hook not being in the body of the component. - // eslint-disable-next-line react-hooks/exhaustive-deps - [state, groupingIds, hiddenIds, sortingIds] - ); - }, - }, + tableConfig, useFlexLayout, useGroupBy, - useSortBy, useExpanded ); - // If the table has a custom sort, the tableState.sortBy has these items prepended. - const customSortCount = tableState.sortBy.length - sortingIds.length; + // If the table has a custom sort, count them + const customSortCount = sortingIds.length; // Desktop row const renderDesktopRow = useCallback( @@ -296,6 +355,8 @@ export const Table = () => { ] ); + console.log({ filteredData }); + return ( <> {showSearch && ( @@ -370,6 +431,8 @@ export const Table = () => { tableColumnsMeta={tableColumnsMeta} customSortCount={customSortCount} totalColumnsWidth={totalColumnsWidth} + handleSortClick={pagination ? handleSortClick : undefined} + manualSortBy={pagination ? manualSortBy : undefined} > {({ height }: { height: number }) => ( @@ -389,13 +452,56 @@ export const Table = () => { )} - - Total number of rows:{" "} - {filteredData.length} - + + Total number of rows:{" "} + {pagination ? pagination.totalCount : filteredData.length} + + + {pagination && ( + + + Page {pagination.pageIndex + 1} of{" "} + {Math.ceil(pagination.totalCount / pagination.pageSize)} + + + + + + + + + )} + ); }; diff --git a/app/graphql/hooks.ts b/app/graphql/hooks.ts index 0d431c1373..1e0453acf7 100644 --- a/app/graphql/hooks.ts +++ b/app/graphql/hooks.ts @@ -32,6 +32,9 @@ import { DataCubeMetadataQueryVariables, DataCubeObservationFilter, DataCubeObservationsDocument, + DataCubeObservationsPaginatedDocument, + DataCubeObservationsPaginatedQuery, + DataCubeObservationsPaginatedQueryVariables, DataCubeObservationsQuery, DataCubeObservationsQueryVariables, } from "./query-hooks"; @@ -732,3 +735,168 @@ export const useDataCubesComponentTermsetsQuery = makeUseQuery< >({ fetch: executeDataCubesTermsetsQuery, }); + +export type DataCubesObservationsPaginatedOptions = { + variables: Omit & { + cubeFilters: DataCubeObservationFilter[]; + }; + pause?: boolean; +}; + +type DataCubesObservationsPaginatedData = { + dataCubesObservationsPaginated: { + data: DataCubesObservations; + pagination: { + hasNextPage: boolean; + hasPreviousPage: boolean; + totalCount?: number | null; + limit?: number | null; + offset?: number | null; + }; + sparqlEditorUrl: string; + }; +}; + +export const executeDataCubesObservationsPaginatedQuery = async ( + client: Client, + variables: DataCubesObservationsPaginatedOptions["variables"], + onFetching?: () => void +) => { + const { locale, sourceType, sourceUrl, cubeFilters } = variables; + + if (cubeFilters.length !== 1) { + throw new Error("Pagination currently only supports single cube queries"); + } + + const cubeFilter = cubeFilters[0]; + const cubeVariables = { locale, sourceType, sourceUrl, cubeFilter }; + + const cached = client.readQuery< + DataCubeObservationsPaginatedQuery, + DataCubeObservationsPaginatedQueryVariables + >(DataCubeObservationsPaginatedDocument, cubeVariables); + + if (cached) { + return Promise.resolve({ + data: { + dataCubesObservationsPaginated: { + data: { + data: cached.data?.dataCubeObservationsPaginated.data.data || [], + sparqlEditorUrls: [ + { + cubeIri: cubeFilter.iri, + url: + cached.data?.dataCubeObservationsPaginated.data.sparqlEditorUrl || + "", + }, + ], + }, + pagination: cached.data?.dataCubeObservationsPaginated.pagination || { + hasNextPage: false, + hasPreviousPage: false, + totalCount: 0, + limit: 0, + offset: 0, + }, + sparqlEditorUrl: + cached.data?.dataCubeObservationsPaginated.sparqlEditorUrl || "", + }, + }, + error: cached.error, + fetching: false, + }); + } + + onFetching?.(); + + const result = await client + .query< + DataCubeObservationsPaginatedQuery, + DataCubeObservationsPaginatedQueryVariables + >(DataCubeObservationsPaginatedDocument, cubeVariables) + .toPromise(); + + if (result.error || !result.data) { + return { + data: undefined, + error: result.error, + fetching: false, + }; + } + + return { + data: { + dataCubesObservationsPaginated: { + data: { + data: result.data.dataCubeObservationsPaginated.data.data, + sparqlEditorUrls: [ + { + cubeIri: cubeFilter.iri, + url: result.data.dataCubeObservationsPaginated.data.sparqlEditorUrl, + }, + ], + }, + pagination: result.data.dataCubeObservationsPaginated.pagination, + sparqlEditorUrl: + result.data.dataCubeObservationsPaginated.sparqlEditorUrl, + }, + }, + error: result.error, + fetching: false, + }; +}; + +const transformDataCubesObservationsPaginated = ( + data: { + data?: DataCubesObservationsPaginatedData | null; + error?: Error; + fetching: boolean; + }, + options: { + locale: string; + conversionUnitsByComponentId: ChartConfig["conversionUnitsByComponentId"]; + } +) => { + const { conversionUnitsByComponentId } = options; + + if ( + !data.data || + !conversionUnitsByComponentId || + Object.keys(conversionUnitsByComponentId).length === 0 + ) { + return data; + } + + const transformedObservations = transformDataCubesObservations( + { + data: { + dataCubesObservations: data.data.dataCubesObservationsPaginated.data, + }, + error: data.error, + fetching: data.fetching, + }, + options + ); + + return { + ...data, + data: { + dataCubesObservationsPaginated: { + data: + transformedObservations.data?.dataCubesObservations || + data.data.dataCubesObservationsPaginated.data, + pagination: data.data.dataCubesObservationsPaginated.pagination, + sparqlEditorUrl: + data.data.dataCubesObservationsPaginated.sparqlEditorUrl, + }, + }, + }; +}; + +export const useDataCubesObservationsPaginatedQuery = makeUseQuery< + DataCubesObservationsPaginatedOptions & { chartConfig: ChartConfig }, + DataCubesObservationsPaginatedData +>({ + fetch: executeDataCubesObservationsPaginatedQuery, + transform: transformDataCubesObservationsPaginated, +}); diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index d7aa7f6576..414d93f528 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -118,6 +118,30 @@ query DataCubeObservations( ) } +query DataCubeObservationsPaginated( + $sourceType: String! + $sourceUrl: DataSourceUrl! + $locale: String! + $cubeFilter: DataCubeObservationFilter! +) { + dataCubeObservationsPaginated( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) { + data + pagination { + hasNextPage + hasPreviousPage + totalCount + limit + offset + } + sparqlEditorUrl + } +} + query DataCubePreview( $sourceType: String! $sourceUrl: DataSourceUrl! diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index 021a570dae..a3348226a1 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -1,30 +1,24 @@ -import { ComponentTermsets } from "../domain/data"; -import { DataCubeComponents } from "../domain/data"; -import { DataCubeMetadata } from "../domain/data"; -import { DataCubeObservations } from "../domain/data"; -import { DataCubePreview } from "../domain/data"; -import { DataSourceUrl } from "../domain/data-source"; -import { DimensionValue } from "../domain/data"; -import { Filters } from "../configurator"; -import { GeoShapes } from "../domain/data"; -import { HierarchyValue } from "../domain/data"; -import { Observation } from "../domain/data"; -import { RawObservation } from "../domain/data"; -import { SearchCube } from "../domain/data"; -import { SingleFilters } from "../configurator"; -import { Termset } from "../domain/data"; -import gql from "graphql-tag"; -import * as Urql from "urql"; +import { ComponentTermsets } from '../domain/data'; +import { DataCubeComponents } from '../domain/data'; +import { DataCubeMetadata } from '../domain/data'; +import { DataCubeObservations } from '../domain/data'; +import { DataCubePreview } from '../domain/data'; +import { DataSourceUrl } from '../domain/data-source'; +import { DimensionValue } from '../domain/data'; +import { Filters } from '../configurator'; +import { GeoShapes } from '../domain/data'; +import { HierarchyValue } from '../domain/data'; +import { Observation } from '../domain/data'; +import { RawObservation } from '../domain/data'; +import { SearchCube } from '../domain/data'; +import { SingleFilters } from '../configurator'; +import { Termset } from '../domain/data'; +import gql from 'graphql-tag'; +import * as Urql from 'urql'; export type Maybe = T | null; -export type Exact = { - [K in keyof T]: T[K]; -}; -export type MakeOptional = Omit & { - [SubKey in K]?: Maybe; -}; -export type MakeMaybe = Omit & { - [SubKey in K]: Maybe; -}; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; export type Omit = Pick>; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { @@ -53,619 +47,572 @@ export type Scalars = { ValuePosition: any; }; + + export type DataCubeComponentFilter = { - iri: Scalars["String"]; - filters?: Maybe; - componentIds?: Maybe>; - joinBy?: Maybe>; - loadValues?: Maybe; + iri: Scalars['String']; + filters?: Maybe; + componentIds?: Maybe>; + joinBy?: Maybe>; + loadValues?: Maybe; }; + export type DataCubeDimensionGeoShapesCubeFilter = { - iri: Scalars["String"]; - dimensionId: Scalars["String"]; + iri: Scalars['String']; + dimensionId: Scalars['String']; }; export type DataCubeLatestIriFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; + export type DataCubeMetadataFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export type DataCubeObservationFilter = { - iri: Scalars["String"]; - filters?: Maybe; - componentIds?: Maybe>; - joinBy?: Maybe>; + iri: Scalars['String']; + filters?: Maybe; + componentIds?: Maybe>; + joinBy?: Maybe>; + limit?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + +export type DataCubeObservationsPage = { + __typename: 'DataCubeObservationsPage'; + data: Scalars['DataCubeObservations']; + pagination: PaginationInfo; + sparqlEditorUrl: Scalars['String']; }; export type DataCubeOrganization = { - __typename: "DataCubeOrganization"; - iri: Scalars["String"]; - label?: Maybe; + __typename: 'DataCubeOrganization'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubePossibleFiltersCubeFilter = { - iri: Scalars["String"]; - filters: Scalars["SingleFilters"]; + iri: Scalars['String']; + filters: Scalars['SingleFilters']; }; + export type DataCubePreviewFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export enum DataCubePublicationStatus { - Draft = "DRAFT", - Published = "PUBLISHED", + Draft = 'DRAFT', + Published = 'PUBLISHED' } export type DataCubeTermset = { - __typename: "DataCubeTermset"; - iri: Scalars["String"]; - label?: Maybe; + __typename: 'DataCubeTermset'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubeTermsetFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export type DataCubeTheme = { - __typename: "DataCubeTheme"; - iri: Scalars["String"]; - label?: Maybe; + __typename: 'DataCubeTheme'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubeUnversionedIriFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; +}; + + + + + + + + +export type OrderByClause = { + componentId: Scalars['String']; + order: SortOrder; +}; + +export type PaginationInfo = { + __typename: 'PaginationInfo'; + hasNextPage: Scalars['Boolean']; + hasPreviousPage: Scalars['Boolean']; + totalCount?: Maybe; + limit?: Maybe; + offset?: Maybe; }; export type PossibleFilterValue = { - __typename: "PossibleFilterValue"; - type: Scalars["String"]; - id: Scalars["String"]; - value?: Maybe; + __typename: 'PossibleFilterValue'; + type: Scalars['String']; + id: Scalars['String']; + value?: Maybe; }; export type Query = { - __typename: "Query"; - dataCubeLatestIri: Scalars["String"]; - dataCubeUnversionedIri?: Maybe; - dataCubeComponents: Scalars["DataCubeComponents"]; - dataCubeComponentTermsets: Array; - dataCubeMetadata: Scalars["DataCubeMetadata"]; - dataCubeObservations: Scalars["DataCubeObservations"]; - dataCubePreview: Scalars["DataCubePreview"]; + __typename: 'Query'; + dataCubeLatestIri: Scalars['String']; + dataCubeUnversionedIri?: Maybe; + dataCubeComponents: Scalars['DataCubeComponents']; + dataCubeComponentTermsets: Array; + dataCubeMetadata: Scalars['DataCubeMetadata']; + dataCubeObservations: Scalars['DataCubeObservations']; + dataCubeObservationsPaginated: DataCubeObservationsPage; + dataCubePreview: Scalars['DataCubePreview']; possibleFilters: Array; searchCubes: Array; - dataCubeDimensionGeoShapes?: Maybe; + dataCubeDimensionGeoShapes?: Maybe; }; + export type QueryDataCubeLatestIriArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubeLatestIriFilter; }; + export type QueryDataCubeUnversionedIriArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; cubeFilter: DataCubeUnversionedIriFilter; }; + export type QueryDataCubeComponentsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeComponentFilter; }; + export type QueryDataCubeComponentTermsetsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeTermsetFilter; }; + export type QueryDataCubeMetadataArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeMetadataFilter; }; + export type QueryDataCubeObservationsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeObservationFilter; }; + +export type QueryDataCubeObservationsPaginatedArgs = { + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; + cubeFilter: DataCubeObservationFilter; +}; + + export type QueryDataCubePreviewArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubePreviewFilter; }; + export type QueryPossibleFiltersArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubePossibleFiltersCubeFilter; }; + export type QuerySearchCubesArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale?: Maybe; - query?: Maybe; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale?: Maybe; + query?: Maybe; order?: Maybe; - includeDrafts?: Maybe; - fetchDimensionTermsets?: Maybe; + includeDrafts?: Maybe; + fetchDimensionTermsets?: Maybe; filters?: Maybe>; }; + export type QueryDataCubeDimensionGeoShapesArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeDimensionGeoShapesCubeFilter; }; + export type RelatedDimension = { - __typename: "RelatedDimension"; + __typename: 'RelatedDimension'; type: RelatedDimensionType; - id: Scalars["String"]; + id: Scalars['String']; }; export enum RelatedDimensionType { - StandardError = "StandardError", - ConfidenceUpperBound = "ConfidenceUpperBound", - ConfidenceLowerBound = "ConfidenceLowerBound", + StandardError = 'StandardError', + ConfidenceUpperBound = 'ConfidenceUpperBound', + ConfidenceLowerBound = 'ConfidenceLowerBound' } export enum ScaleType { - Ordinal = "Ordinal", - Nominal = "Nominal", - Interval = "Interval", - Ratio = "Ratio", + Ordinal = 'Ordinal', + Nominal = 'Nominal', + Interval = 'Interval', + Ratio = 'Ratio' } + export type SearchCubeFilter = { type: SearchCubeFilterType; - label?: Maybe; - value: Scalars["String"]; + label?: Maybe; + value: Scalars['String']; }; export enum SearchCubeFilterType { - TemporalDimension = "TemporalDimension", - DataCubeTheme = "DataCubeTheme", - DataCubeOrganization = "DataCubeOrganization", - DataCubeAbout = "DataCubeAbout", - DataCubeTermset = "DataCubeTermset", + TemporalDimension = 'TemporalDimension', + DataCubeTheme = 'DataCubeTheme', + DataCubeOrganization = 'DataCubeOrganization', + DataCubeAbout = 'DataCubeAbout', + DataCubeTermset = 'DataCubeTermset' } export type SearchCubeResult = { - __typename: "SearchCubeResult"; - score?: Maybe; - cube: Scalars["SearchCube"]; - highlightedTitle?: Maybe; - highlightedDescription?: Maybe; + __typename: 'SearchCubeResult'; + score?: Maybe; + cube: Scalars['SearchCube']; + highlightedTitle?: Maybe; + highlightedDescription?: Maybe; }; export enum SearchCubeResultOrder { - Score = "SCORE", - TitleAsc = "TITLE_ASC", - CreatedDesc = "CREATED_DESC", + Score = 'SCORE', + TitleAsc = 'TITLE_ASC', + CreatedDesc = 'CREATED_DESC' +} + + +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC' } + export enum TimeUnit { - Year = "Year", - Month = "Month", - Week = "Week", - Day = "Day", - Hour = "Hour", - Minute = "Minute", - Second = "Second", + Year = 'Year', + Month = 'Month', + Week = 'Week', + Day = 'Day', + Hour = 'Hour', + Minute = 'Minute', + Second = 'Second' } + + export type SearchCubesQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; - query?: Maybe; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; + query?: Maybe; order?: Maybe; - includeDrafts?: Maybe; - fetchDimensionTermsets?: 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 SearchCubesQuery = { __typename: 'Query', searchCubes: Array<{ __typename: 'SearchCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, cube: SearchCube }> }; export type DataCubeLatestIriQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubeLatestIriFilter; }>; -export type DataCubeLatestIriQuery = { - __typename: "Query"; - dataCubeLatestIri: string; -}; + +export type DataCubeLatestIriQuery = { __typename: 'Query', dataCubeLatestIri: string }; export type DataCubeUnversionedIriQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; cubeFilter: DataCubeUnversionedIriFilter; }>; -export type DataCubeUnversionedIriQuery = { - __typename: "Query"; - dataCubeUnversionedIri?: Maybe; -}; + +export type DataCubeUnversionedIriQuery = { __typename: 'Query', dataCubeUnversionedIri?: Maybe }; export type DataCubeComponentsQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeComponentFilter; }>; -export type DataCubeComponentsQuery = { - __typename: "Query"; - dataCubeComponents: DataCubeComponents; -}; + +export type DataCubeComponentsQuery = { __typename: 'Query', dataCubeComponents: DataCubeComponents }; export type DataCubeDimensionGeoShapesQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeDimensionGeoShapesCubeFilter; }>; -export type DataCubeDimensionGeoShapesQuery = { - __typename: "Query"; - dataCubeDimensionGeoShapes?: Maybe; -}; + +export type DataCubeDimensionGeoShapesQuery = { __typename: 'Query', dataCubeDimensionGeoShapes?: Maybe }; export type DataCubeMetadataQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeMetadataFilter; }>; -export type DataCubeMetadataQuery = { - __typename: "Query"; - dataCubeMetadata: DataCubeMetadata; -}; + +export type DataCubeMetadataQuery = { __typename: 'Query', dataCubeMetadata: DataCubeMetadata }; export type DataCubeComponentTermsetsQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeTermsetFilter; }>; -export type DataCubeComponentTermsetsQuery = { - __typename: "Query"; - dataCubeComponentTermsets: Array; -}; + +export type DataCubeComponentTermsetsQuery = { __typename: 'Query', dataCubeComponentTermsets: Array }; export type DataCubeObservationsQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeObservationFilter; }>; -export type DataCubeObservationsQuery = { - __typename: "Query"; - dataCubeObservations: DataCubeObservations; -}; + +export type DataCubeObservationsQuery = { __typename: 'Query', dataCubeObservations: DataCubeObservations }; + +export type DataCubeObservationsPaginatedQueryVariables = Exact<{ + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; + cubeFilter: DataCubeObservationFilter; +}>; + + +export type DataCubeObservationsPaginatedQuery = { __typename: 'Query', dataCubeObservationsPaginated: { __typename: 'DataCubeObservationsPage', data: DataCubeObservations, sparqlEditorUrl: string, pagination: { __typename: 'PaginationInfo', hasNextPage: boolean, hasPreviousPage: boolean, totalCount?: Maybe, limit?: Maybe, offset?: Maybe } } }; export type DataCubePreviewQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubePreviewFilter; }>; -export type DataCubePreviewQuery = { - __typename: "Query"; - dataCubePreview: DataCubePreview; -}; + +export type DataCubePreviewQuery = { __typename: 'Query', dataCubePreview: DataCubePreview }; export type PossibleFiltersQueryVariables = Exact<{ - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubePossibleFiltersCubeFilter; }>; -export type PossibleFiltersQuery = { - __typename: "Query"; - possibleFilters: Array<{ - __typename: "PossibleFilterValue"; - type: string; - id: string; - value?: Maybe; - }>; -}; + +export type PossibleFiltersQuery = { __typename: 'Query', possibleFilters: Array<{ __typename: 'PossibleFilterValue', type: string, id: string, value?: Maybe }> }; + export const SearchCubesDocument = gql` - query SearchCubes( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $locale: String! - $query: String - $order: SearchCubeResultOrder - $includeDrafts: Boolean - $fetchDimensionTermsets: Boolean - $filters: [SearchCubeFilter!] + query SearchCubes($sourceType: String!, $sourceUrl: DataSourceUrl!, $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 ) { - searchCubes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - query: $query - order: $order - includeDrafts: $includeDrafts - fetchDimensionTermsets: $fetchDimensionTermsets - filters: $filters - ) { - highlightedTitle - highlightedDescription - cube - } + highlightedTitle + highlightedDescription + cube } -`; - -export function useSearchCubesQuery( - options: Omit, "query"> = {} -) { - return Urql.useQuery({ - query: SearchCubesDocument, - ...options, - }); } + `; + +export function useSearchCubesQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: SearchCubesDocument, ...options }); +}; export const DataCubeLatestIriDocument = gql` - query DataCubeLatestIri( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $cubeFilter: DataCubeLatestIriFilter! - ) { - dataCubeLatestIri( - sourceType: $sourceType - sourceUrl: $sourceUrl - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeLatestIriQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeLatestIriDocument, - ...options, - }); + query DataCubeLatestIri($sourceType: String!, $sourceUrl: DataSourceUrl!, $cubeFilter: DataCubeLatestIriFilter!) { + dataCubeLatestIri( + sourceType: $sourceType + sourceUrl: $sourceUrl + cubeFilter: $cubeFilter + ) } + `; + +export function useDataCubeLatestIriQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeLatestIriDocument, ...options }); +}; export const DataCubeUnversionedIriDocument = gql` - query DataCubeUnversionedIri( - $sourceType: String! - $sourceUrl: String! - $cubeFilter: DataCubeUnversionedIriFilter! - ) { - dataCubeUnversionedIri( - sourceType: $sourceType - sourceUrl: $sourceUrl - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeUnversionedIriQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeUnversionedIriDocument, - ...options, - }); + query DataCubeUnversionedIri($sourceType: String!, $sourceUrl: String!, $cubeFilter: DataCubeUnversionedIriFilter!) { + dataCubeUnversionedIri( + sourceType: $sourceType + sourceUrl: $sourceUrl + cubeFilter: $cubeFilter + ) } + `; + +export function useDataCubeUnversionedIriQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeUnversionedIriDocument, ...options }); +}; export const DataCubeComponentsDocument = gql` - query DataCubeComponents( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $locale: String! - $cubeFilter: DataCubeComponentFilter! - ) { - dataCubeComponents( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeComponentsQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeComponentsDocument, - ...options, - }); + query DataCubeComponents($sourceType: String!, $sourceUrl: DataSourceUrl!, $locale: String!, $cubeFilter: DataCubeComponentFilter!) { + dataCubeComponents( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) } + `; + +export function useDataCubeComponentsQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeComponentsDocument, ...options }); +}; export const DataCubeDimensionGeoShapesDocument = gql` - query DataCubeDimensionGeoShapes( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $locale: String! - $cubeFilter: DataCubeDimensionGeoShapesCubeFilter! - ) { - dataCubeDimensionGeoShapes( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeDimensionGeoShapesQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeDimensionGeoShapesDocument, - ...options, - }); + query DataCubeDimensionGeoShapes($sourceType: String!, $sourceUrl: DataSourceUrl!, $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: DataSourceUrl! - $locale: String! - $cubeFilter: DataCubeMetadataFilter! - ) { - dataCubeMetadata( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeMetadataQuery( - options: Omit, "query"> = {} -) { - return Urql.useQuery({ - query: DataCubeMetadataDocument, - ...options, - }); + query DataCubeMetadata($sourceType: String!, $sourceUrl: DataSourceUrl!, $locale: String!, $cubeFilter: DataCubeMetadataFilter!) { + dataCubeMetadata( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) } + `; + +export function useDataCubeMetadataQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeMetadataDocument, ...options }); +}; export const DataCubeComponentTermsetsDocument = gql` - query DataCubeComponentTermsets( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $locale: String! - $cubeFilter: DataCubeTermsetFilter! - ) { - dataCubeComponentTermsets( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) - } -`; - -export function useDataCubeComponentTermsetsQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeComponentTermsetsDocument, - ...options, - }); + query DataCubeComponentTermsets($sourceType: String!, $sourceUrl: DataSourceUrl!, $locale: String!, $cubeFilter: DataCubeTermsetFilter!) { + dataCubeComponentTermsets( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) } + `; + +export function useDataCubeComponentTermsetsQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeComponentTermsetsDocument, ...options }); +}; export const DataCubeObservationsDocument = gql` - query DataCubeObservations( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $locale: String! - $cubeFilter: DataCubeObservationFilter! + query DataCubeObservations($sourceType: String!, $sourceUrl: DataSourceUrl!, $locale: String!, $cubeFilter: DataCubeObservationFilter!) { + dataCubeObservations( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter + ) +} + `; + +export function useDataCubeObservationsQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeObservationsDocument, ...options }); +}; +export const DataCubeObservationsPaginatedDocument = gql` + query DataCubeObservationsPaginated($sourceType: String!, $sourceUrl: DataSourceUrl!, $locale: String!, $cubeFilter: DataCubeObservationFilter!) { + dataCubeObservationsPaginated( + sourceType: $sourceType + sourceUrl: $sourceUrl + locale: $locale + cubeFilter: $cubeFilter ) { - dataCubeObservations( - sourceType: $sourceType - sourceUrl: $sourceUrl - locale: $locale - cubeFilter: $cubeFilter - ) + data + pagination { + hasNextPage + hasPreviousPage + totalCount + limit + offset + } + sparqlEditorUrl } -`; - -export function useDataCubeObservationsQuery( - options: Omit< - Urql.UseQueryArgs, - "query" - > = {} -) { - return Urql.useQuery({ - query: DataCubeObservationsDocument, - ...options, - }); } + `; + +export function useDataCubeObservationsPaginatedQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: DataCubeObservationsPaginatedDocument, ...options }); +}; export const DataCubePreviewDocument = gql` - query DataCubePreview( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $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, - }); + query DataCubePreview($sourceType: String!, $sourceUrl: DataSourceUrl!, $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 PossibleFiltersDocument = gql` - query PossibleFilters( - $sourceType: String! - $sourceUrl: DataSourceUrl! - $cubeFilter: DataCubePossibleFiltersCubeFilter! + query PossibleFilters($sourceType: String!, $sourceUrl: DataSourceUrl!, $cubeFilter: DataCubePossibleFiltersCubeFilter!) { + possibleFilters( + sourceType: $sourceType + sourceUrl: $sourceUrl + cubeFilter: $cubeFilter ) { - possibleFilters( - sourceType: $sourceType - sourceUrl: $sourceUrl - cubeFilter: $cubeFilter - ) { - type - id - value - } + type + id + value } -`; - -export function usePossibleFiltersQuery( - options: Omit, "query"> = {} -) { - return Urql.useQuery({ - query: PossibleFiltersDocument, - ...options, - }); } + `; + +export function usePossibleFiltersQuery(options: Omit, 'query'> = {}) { + return Urql.useQuery({ query: PossibleFiltersDocument, ...options }); +}; \ No newline at end of file diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index 8d78bf9c7d..ce5eedb5ee 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -1,37 +1,25 @@ -import { ComponentTermsets } from "../domain/data"; -import { DataCubeComponents } from "../domain/data"; -import { DataCubeMetadata } from "../domain/data"; -import { DataCubeObservations } from "../domain/data"; -import { DataCubePreview } from "../domain/data"; -import { DataSourceUrl } from "../domain/data-source"; -import { DimensionValue } from "../domain/data"; -import { Filters } from "../configurator"; -import { GeoShapes } from "../domain/data"; -import { HierarchyValue } from "../domain/data"; -import { Observation } from "../domain/data"; -import { RawObservation } from "../domain/data"; -import { SearchCube } from "../domain/data"; -import { SingleFilters } from "../configurator"; -import { Termset } from "../domain/data"; -import { - GraphQLResolveInfo, - GraphQLScalarType, - GraphQLScalarTypeConfig, -} from "graphql"; -import { VisualizeGraphQLContext } from "./context"; +import { ComponentTermsets } from '../domain/data'; +import { DataCubeComponents } from '../domain/data'; +import { DataCubeMetadata } from '../domain/data'; +import { DataCubeObservations } from '../domain/data'; +import { DataCubePreview } from '../domain/data'; +import { DataSourceUrl } from '../domain/data-source'; +import { DimensionValue } from '../domain/data'; +import { Filters } from '../configurator'; +import { GeoShapes } from '../domain/data'; +import { HierarchyValue } from '../domain/data'; +import { Observation } from '../domain/data'; +import { RawObservation } from '../domain/data'; +import { SearchCube } from '../domain/data'; +import { SingleFilters } from '../configurator'; +import { Termset } from '../domain/data'; +import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +import { VisualizeGraphQLContext } from './context'; export type Maybe = T | null; -export type Exact = { - [K in keyof T]: T[K]; -}; -export type MakeOptional = Omit & { - [SubKey in K]?: Maybe; -}; -export type MakeMaybe = Omit & { - [SubKey in K]: Maybe; -}; -export type RequireFields = { - [X in Exclude]?: T[X]; -} & { [P in K]-?: NonNullable }; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; @@ -59,234 +47,296 @@ export type Scalars = { ValuePosition: any; }; + + export type DataCubeComponentFilter = { - iri: Scalars["String"]; - filters?: Maybe; - componentIds?: Maybe>; - joinBy?: Maybe>; - loadValues?: Maybe; + iri: Scalars['String']; + filters?: Maybe; + componentIds?: Maybe>; + joinBy?: Maybe>; + loadValues?: Maybe; }; + export type DataCubeDimensionGeoShapesCubeFilter = { - iri: Scalars["String"]; - dimensionId: Scalars["String"]; + iri: Scalars['String']; + dimensionId: Scalars['String']; }; export type DataCubeLatestIriFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; + export type DataCubeMetadataFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export type DataCubeObservationFilter = { - iri: Scalars["String"]; - filters?: Maybe; - componentIds?: Maybe>; - joinBy?: Maybe>; + iri: Scalars['String']; + filters?: Maybe; + componentIds?: Maybe>; + joinBy?: Maybe>; + limit?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + +export type DataCubeObservationsPage = { + __typename?: 'DataCubeObservationsPage'; + data: Scalars['DataCubeObservations']; + pagination: PaginationInfo; + sparqlEditorUrl: Scalars['String']; }; export type DataCubeOrganization = { - __typename?: "DataCubeOrganization"; - iri: Scalars["String"]; - label?: Maybe; + __typename?: 'DataCubeOrganization'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubePossibleFiltersCubeFilter = { - iri: Scalars["String"]; - filters: Scalars["SingleFilters"]; + iri: Scalars['String']; + filters: Scalars['SingleFilters']; }; + export type DataCubePreviewFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export enum DataCubePublicationStatus { - Draft = "DRAFT", - Published = "PUBLISHED", + Draft = 'DRAFT', + Published = 'PUBLISHED' } export type DataCubeTermset = { - __typename?: "DataCubeTermset"; - iri: Scalars["String"]; - label?: Maybe; + __typename?: 'DataCubeTermset'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubeTermsetFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; }; export type DataCubeTheme = { - __typename?: "DataCubeTheme"; - iri: Scalars["String"]; - label?: Maybe; + __typename?: 'DataCubeTheme'; + iri: Scalars['String']; + label?: Maybe; }; export type DataCubeUnversionedIriFilter = { - iri: Scalars["String"]; + iri: Scalars['String']; +}; + + + + + + + + +export type OrderByClause = { + componentId: Scalars['String']; + order: SortOrder; +}; + +export type PaginationInfo = { + __typename?: 'PaginationInfo'; + hasNextPage: Scalars['Boolean']; + hasPreviousPage: Scalars['Boolean']; + totalCount?: Maybe; + limit?: Maybe; + offset?: Maybe; }; export type PossibleFilterValue = { - __typename?: "PossibleFilterValue"; - type: Scalars["String"]; - id: Scalars["String"]; - value?: Maybe; + __typename?: 'PossibleFilterValue'; + type: Scalars['String']; + id: Scalars['String']; + value?: Maybe; }; export type Query = { - __typename?: "Query"; - dataCubeLatestIri: Scalars["String"]; - dataCubeUnversionedIri?: Maybe; - dataCubeComponents: Scalars["DataCubeComponents"]; - dataCubeComponentTermsets: Array; - dataCubeMetadata: Scalars["DataCubeMetadata"]; - dataCubeObservations: Scalars["DataCubeObservations"]; - dataCubePreview: Scalars["DataCubePreview"]; + __typename?: 'Query'; + dataCubeLatestIri: Scalars['String']; + dataCubeUnversionedIri?: Maybe; + dataCubeComponents: Scalars['DataCubeComponents']; + dataCubeComponentTermsets: Array; + dataCubeMetadata: Scalars['DataCubeMetadata']; + dataCubeObservations: Scalars['DataCubeObservations']; + dataCubeObservationsPaginated: DataCubeObservationsPage; + dataCubePreview: Scalars['DataCubePreview']; possibleFilters: Array; searchCubes: Array; - dataCubeDimensionGeoShapes?: Maybe; + dataCubeDimensionGeoShapes?: Maybe; }; + export type QueryDataCubeLatestIriArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubeLatestIriFilter; }; + export type QueryDataCubeUnversionedIriArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['String']; cubeFilter: DataCubeUnversionedIriFilter; }; + export type QueryDataCubeComponentsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeComponentFilter; }; + export type QueryDataCubeComponentTermsetsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeTermsetFilter; }; + export type QueryDataCubeMetadataArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeMetadataFilter; }; + export type QueryDataCubeObservationsArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeObservationFilter; }; + +export type QueryDataCubeObservationsPaginatedArgs = { + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; + cubeFilter: DataCubeObservationFilter; +}; + + export type QueryDataCubePreviewArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubePreviewFilter; }; + export type QueryPossibleFiltersArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; cubeFilter: DataCubePossibleFiltersCubeFilter; }; + export type QuerySearchCubesArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale?: Maybe; - query?: Maybe; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale?: Maybe; + query?: Maybe; order?: Maybe; - includeDrafts?: Maybe; - fetchDimensionTermsets?: Maybe; + includeDrafts?: Maybe; + fetchDimensionTermsets?: Maybe; filters?: Maybe>; }; + export type QueryDataCubeDimensionGeoShapesArgs = { - sourceType: Scalars["String"]; - sourceUrl: Scalars["DataSourceUrl"]; - locale: Scalars["String"]; + sourceType: Scalars['String']; + sourceUrl: Scalars['DataSourceUrl']; + locale: Scalars['String']; cubeFilter: DataCubeDimensionGeoShapesCubeFilter; }; + export type RelatedDimension = { - __typename?: "RelatedDimension"; + __typename?: 'RelatedDimension'; type: RelatedDimensionType; - id: Scalars["String"]; + id: Scalars['String']; }; export enum RelatedDimensionType { - StandardError = "StandardError", - ConfidenceUpperBound = "ConfidenceUpperBound", - ConfidenceLowerBound = "ConfidenceLowerBound", + StandardError = 'StandardError', + ConfidenceUpperBound = 'ConfidenceUpperBound', + ConfidenceLowerBound = 'ConfidenceLowerBound' } export enum ScaleType { - Ordinal = "Ordinal", - Nominal = "Nominal", - Interval = "Interval", - Ratio = "Ratio", + Ordinal = 'Ordinal', + Nominal = 'Nominal', + Interval = 'Interval', + Ratio = 'Ratio' } + export type SearchCubeFilter = { type: SearchCubeFilterType; - label?: Maybe; - value: Scalars["String"]; + label?: Maybe; + value: Scalars['String']; }; export enum SearchCubeFilterType { - TemporalDimension = "TemporalDimension", - DataCubeTheme = "DataCubeTheme", - DataCubeOrganization = "DataCubeOrganization", - DataCubeAbout = "DataCubeAbout", - DataCubeTermset = "DataCubeTermset", + TemporalDimension = 'TemporalDimension', + DataCubeTheme = 'DataCubeTheme', + DataCubeOrganization = 'DataCubeOrganization', + DataCubeAbout = 'DataCubeAbout', + DataCubeTermset = 'DataCubeTermset' } export type SearchCubeResult = { - __typename?: "SearchCubeResult"; - score?: Maybe; - cube: Scalars["SearchCube"]; - highlightedTitle?: Maybe; - highlightedDescription?: Maybe; + __typename?: 'SearchCubeResult'; + score?: Maybe; + cube: Scalars['SearchCube']; + highlightedTitle?: Maybe; + highlightedDescription?: Maybe; }; export enum SearchCubeResultOrder { - Score = "SCORE", - TitleAsc = "TITLE_ASC", - CreatedDesc = "CREATED_DESC", + Score = 'SCORE', + TitleAsc = 'TITLE_ASC', + CreatedDesc = 'CREATED_DESC' } + +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC' +} + + export enum TimeUnit { - Year = "Year", - Month = "Month", - Week = "Week", - Day = "Day", - Hour = "Hour", - Minute = "Minute", - Second = "Second", + Year = 'Year', + Month = 'Month', + Week = 'Week', + Day = 'Day', + Hour = 'Hour', + Minute = 'Minute', + Second = 'Second' } + + export type WithIndex = TObject & Record; export type ResolversObject = WithIndex; export type ResolverTypeWrapper = Promise | T; -export type Resolver< - TResult, - TParent = {}, - TContext = {}, - TArgs = {}, -> = ResolverFn; +export type Resolver = ResolverFn; export type ResolverFn = ( parent: TParent, @@ -309,25 +359,9 @@ export type SubscriptionResolveFn = ( info: GraphQLResolveInfo ) => TResult | Promise; -export interface SubscriptionSubscriberObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs, -> { - subscribe: SubscriptionSubscribeFn< - { [key in TKey]: TResult }, - TParent, - TContext, - TArgs - >; - resolve?: SubscriptionResolveFn< - TResult, - { [key in TKey]: TResult }, - TContext, - TArgs - >; +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; } export interface SubscriptionResolverObject { @@ -335,26 +369,12 @@ export interface SubscriptionResolverObject { resolve: SubscriptionResolveFn; } -export type SubscriptionObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs, -> = +export type SubscriptionObject = | SubscriptionSubscriberObject | SubscriptionResolverObject; -export type SubscriptionResolver< - TResult, - TKey extends string, - TParent = {}, - TContext = {}, - TArgs = {}, -> = - | (( - ...args: any[] - ) => SubscriptionObject) +export type SubscriptionResolver = + | ((...args: any[]) => SubscriptionObject) | SubscriptionObject; export type TypeResolveFn = ( @@ -363,20 +383,11 @@ export type TypeResolveFn = ( info: GraphQLResolveInfo ) => Maybe | Promise>; -export type IsTypeOfResolverFn = ( - obj: T, - context: TContext, - info: GraphQLResolveInfo -) => boolean | Promise; +export type IsTypeOfResolverFn = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise; export type NextResolverFn = () => Promise; -export type DirectiveResolverFn< - TResult = {}, - TParent = {}, - TContext = {}, - TArgs = {}, -> = ( +export type DirectiveResolverFn = ( next: NextResolverFn, parent: TParent, args: TArgs, @@ -386,364 +397,246 @@ export type DirectiveResolverFn< /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ - ComponentTermsets: ResolverTypeWrapper; + ComponentTermsets: ResolverTypeWrapper; DataCubeComponentFilter: DataCubeComponentFilter; - String: ResolverTypeWrapper; - Boolean: ResolverTypeWrapper; - DataCubeComponents: ResolverTypeWrapper; + String: ResolverTypeWrapper; + Boolean: ResolverTypeWrapper; + DataCubeComponents: ResolverTypeWrapper; DataCubeDimensionGeoShapesCubeFilter: DataCubeDimensionGeoShapesCubeFilter; DataCubeLatestIriFilter: DataCubeLatestIriFilter; - DataCubeMetadata: ResolverTypeWrapper; + DataCubeMetadata: ResolverTypeWrapper; DataCubeMetadataFilter: DataCubeMetadataFilter; DataCubeObservationFilter: DataCubeObservationFilter; - DataCubeObservations: ResolverTypeWrapper; + Int: ResolverTypeWrapper; + DataCubeObservations: ResolverTypeWrapper; + DataCubeObservationsPage: ResolverTypeWrapper; DataCubeOrganization: ResolverTypeWrapper; DataCubePossibleFiltersCubeFilter: DataCubePossibleFiltersCubeFilter; - DataCubePreview: ResolverTypeWrapper; + DataCubePreview: ResolverTypeWrapper; DataCubePreviewFilter: DataCubePreviewFilter; DataCubePublicationStatus: DataCubePublicationStatus; DataCubeTermset: ResolverTypeWrapper; DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: ResolverTypeWrapper; DataCubeUnversionedIriFilter: DataCubeUnversionedIriFilter; - DataSourceUrl: ResolverTypeWrapper; - DimensionValue: ResolverTypeWrapper; - FilterValue: ResolverTypeWrapper; - Filters: ResolverTypeWrapper; - GeoShapes: ResolverTypeWrapper; - HierarchyValue: ResolverTypeWrapper; - Observation: ResolverTypeWrapper; + DataSourceUrl: ResolverTypeWrapper; + DimensionValue: ResolverTypeWrapper; + FilterValue: ResolverTypeWrapper; + Filters: ResolverTypeWrapper; + GeoShapes: ResolverTypeWrapper; + HierarchyValue: ResolverTypeWrapper; + Observation: ResolverTypeWrapper; + OrderByClause: OrderByClause; + PaginationInfo: ResolverTypeWrapper; PossibleFilterValue: ResolverTypeWrapper; Query: ResolverTypeWrapper<{}>; - RawObservation: ResolverTypeWrapper; + RawObservation: ResolverTypeWrapper; RelatedDimension: ResolverTypeWrapper; RelatedDimensionType: RelatedDimensionType; ScaleType: ScaleType; - SearchCube: ResolverTypeWrapper; + SearchCube: ResolverTypeWrapper; SearchCubeFilter: SearchCubeFilter; SearchCubeFilterType: SearchCubeFilterType; SearchCubeResult: ResolverTypeWrapper; - Float: ResolverTypeWrapper; + Float: ResolverTypeWrapper; SearchCubeResultOrder: SearchCubeResultOrder; - SingleFilters: ResolverTypeWrapper; - Termset: ResolverTypeWrapper; + SingleFilters: ResolverTypeWrapper; + SortOrder: SortOrder; + Termset: ResolverTypeWrapper; TimeUnit: TimeUnit; - ValueIdentifier: ResolverTypeWrapper; - ValuePosition: ResolverTypeWrapper; + ValueIdentifier: ResolverTypeWrapper; + ValuePosition: ResolverTypeWrapper; }>; /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = ResolversObject<{ - ComponentTermsets: Scalars["ComponentTermsets"]; + ComponentTermsets: Scalars['ComponentTermsets']; DataCubeComponentFilter: DataCubeComponentFilter; - String: Scalars["String"]; - Boolean: Scalars["Boolean"]; - DataCubeComponents: Scalars["DataCubeComponents"]; + String: Scalars['String']; + Boolean: Scalars['Boolean']; + DataCubeComponents: Scalars['DataCubeComponents']; DataCubeDimensionGeoShapesCubeFilter: DataCubeDimensionGeoShapesCubeFilter; DataCubeLatestIriFilter: DataCubeLatestIriFilter; - DataCubeMetadata: Scalars["DataCubeMetadata"]; + DataCubeMetadata: Scalars['DataCubeMetadata']; DataCubeMetadataFilter: DataCubeMetadataFilter; DataCubeObservationFilter: DataCubeObservationFilter; - DataCubeObservations: Scalars["DataCubeObservations"]; + Int: Scalars['Int']; + DataCubeObservations: Scalars['DataCubeObservations']; + DataCubeObservationsPage: DataCubeObservationsPage; DataCubeOrganization: DataCubeOrganization; DataCubePossibleFiltersCubeFilter: DataCubePossibleFiltersCubeFilter; - DataCubePreview: Scalars["DataCubePreview"]; + DataCubePreview: Scalars['DataCubePreview']; DataCubePreviewFilter: DataCubePreviewFilter; DataCubeTermset: DataCubeTermset; DataCubeTermsetFilter: DataCubeTermsetFilter; DataCubeTheme: DataCubeTheme; DataCubeUnversionedIriFilter: DataCubeUnversionedIriFilter; - DataSourceUrl: Scalars["DataSourceUrl"]; - DimensionValue: Scalars["DimensionValue"]; - FilterValue: Scalars["FilterValue"]; - Filters: Scalars["Filters"]; - GeoShapes: Scalars["GeoShapes"]; - HierarchyValue: Scalars["HierarchyValue"]; - Observation: Scalars["Observation"]; + DataSourceUrl: Scalars['DataSourceUrl']; + DimensionValue: Scalars['DimensionValue']; + FilterValue: Scalars['FilterValue']; + Filters: Scalars['Filters']; + GeoShapes: Scalars['GeoShapes']; + HierarchyValue: Scalars['HierarchyValue']; + Observation: Scalars['Observation']; + OrderByClause: OrderByClause; + PaginationInfo: PaginationInfo; PossibleFilterValue: PossibleFilterValue; Query: {}; - RawObservation: Scalars["RawObservation"]; + RawObservation: Scalars['RawObservation']; RelatedDimension: RelatedDimension; - SearchCube: Scalars["SearchCube"]; + SearchCube: Scalars['SearchCube']; SearchCubeFilter: SearchCubeFilter; SearchCubeResult: SearchCubeResult; - Float: Scalars["Float"]; - SingleFilters: Scalars["SingleFilters"]; - Termset: Scalars["Termset"]; - ValueIdentifier: Scalars["ValueIdentifier"]; - ValuePosition: Scalars["ValuePosition"]; + Float: Scalars['Float']; + SingleFilters: Scalars['SingleFilters']; + Termset: Scalars['Termset']; + ValueIdentifier: Scalars['ValueIdentifier']; + ValuePosition: Scalars['ValuePosition']; }>; -export type SafeUrlDirectiveArgs = { pattern?: Maybe }; +export type SafeUrlDirectiveArgs = { pattern?: Maybe; }; -export type SafeUrlDirectiveResolver< - Result, - Parent, - ContextType = VisualizeGraphQLContext, - Args = SafeUrlDirectiveArgs, -> = DirectiveResolverFn; +export type SafeUrlDirectiveResolver = DirectiveResolverFn; -export interface ComponentTermsetsScalarConfig - extends GraphQLScalarTypeConfig { - name: "ComponentTermsets"; +export interface ComponentTermsetsScalarConfig extends GraphQLScalarTypeConfig { + name: 'ComponentTermsets'; } -export interface DataCubeComponentsScalarConfig - extends GraphQLScalarTypeConfig { - name: "DataCubeComponents"; +export interface DataCubeComponentsScalarConfig extends GraphQLScalarTypeConfig { + name: 'DataCubeComponents'; } -export interface DataCubeMetadataScalarConfig - extends GraphQLScalarTypeConfig { - name: "DataCubeMetadata"; +export interface DataCubeMetadataScalarConfig extends GraphQLScalarTypeConfig { + name: 'DataCubeMetadata'; } -export interface DataCubeObservationsScalarConfig - extends GraphQLScalarTypeConfig { - name: "DataCubeObservations"; +export interface DataCubeObservationsScalarConfig extends GraphQLScalarTypeConfig { + name: 'DataCubeObservations'; } -export type DataCubeOrganizationResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["DataCubeOrganization"] = ResolversParentTypes["DataCubeOrganization"], -> = ResolversObject<{ - iri?: Resolver; - label?: Resolver, ParentType, ContextType>; +export type DataCubeObservationsPageResolvers = ResolversObject<{ + data?: Resolver; + pagination?: Resolver; + sparqlEditorUrl?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DataCubeOrganizationResolvers = ResolversObject<{ + iri?: Resolver; + label?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export interface DataCubePreviewScalarConfig - extends GraphQLScalarTypeConfig { - name: "DataCubePreview"; +export interface DataCubePreviewScalarConfig extends GraphQLScalarTypeConfig { + name: 'DataCubePreview'; } -export type DataCubeTermsetResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["DataCubeTermset"] = ResolversParentTypes["DataCubeTermset"], -> = ResolversObject<{ - iri?: Resolver; - label?: Resolver, ParentType, ContextType>; +export type DataCubeTermsetResolvers = ResolversObject<{ + iri?: Resolver; + label?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type DataCubeThemeResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["DataCubeTheme"] = ResolversParentTypes["DataCubeTheme"], -> = ResolversObject<{ - iri?: Resolver; - label?: Resolver, ParentType, ContextType>; +export type DataCubeThemeResolvers = ResolversObject<{ + iri?: Resolver; + label?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export interface DataSourceUrlScalarConfig - extends GraphQLScalarTypeConfig { - name: "DataSourceUrl"; +export interface DataSourceUrlScalarConfig extends GraphQLScalarTypeConfig { + name: 'DataSourceUrl'; } -export interface DimensionValueScalarConfig - extends GraphQLScalarTypeConfig { - name: "DimensionValue"; +export interface DimensionValueScalarConfig extends GraphQLScalarTypeConfig { + name: 'DimensionValue'; } -export interface FilterValueScalarConfig - extends GraphQLScalarTypeConfig { - name: "FilterValue"; +export interface FilterValueScalarConfig extends GraphQLScalarTypeConfig { + name: 'FilterValue'; } -export interface FiltersScalarConfig - extends GraphQLScalarTypeConfig { - name: "Filters"; +export interface FiltersScalarConfig extends GraphQLScalarTypeConfig { + name: 'Filters'; } -export interface GeoShapesScalarConfig - extends GraphQLScalarTypeConfig { - name: "GeoShapes"; +export interface GeoShapesScalarConfig extends GraphQLScalarTypeConfig { + name: 'GeoShapes'; } -export interface HierarchyValueScalarConfig - extends GraphQLScalarTypeConfig { - name: "HierarchyValue"; +export interface HierarchyValueScalarConfig extends GraphQLScalarTypeConfig { + name: 'HierarchyValue'; } -export interface ObservationScalarConfig - extends GraphQLScalarTypeConfig { - name: "Observation"; +export interface ObservationScalarConfig extends GraphQLScalarTypeConfig { + name: 'Observation'; } -export type PossibleFilterValueResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["PossibleFilterValue"] = ResolversParentTypes["PossibleFilterValue"], -> = ResolversObject<{ - type?: Resolver; - id?: Resolver; - value?: Resolver< - Maybe, - ParentType, - ContextType - >; +export type PaginationInfoResolvers = ResolversObject<{ + hasNextPage?: Resolver; + hasPreviousPage?: Resolver; + totalCount?: Resolver, ParentType, ContextType>; + limit?: Resolver, ParentType, ContextType>; + offset?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type QueryResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["Query"] = ResolversParentTypes["Query"], -> = ResolversObject<{ - dataCubeLatestIri?: Resolver< - ResolversTypes["String"], - ParentType, - ContextType, - RequireFields< - QueryDataCubeLatestIriArgs, - "sourceType" | "sourceUrl" | "cubeFilter" - > - >; - dataCubeUnversionedIri?: Resolver< - Maybe, - ParentType, - ContextType, - RequireFields< - QueryDataCubeUnversionedIriArgs, - "sourceType" | "sourceUrl" | "cubeFilter" - > - >; - dataCubeComponents?: Resolver< - ResolversTypes["DataCubeComponents"], - ParentType, - ContextType, - RequireFields< - QueryDataCubeComponentsArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; - dataCubeComponentTermsets?: Resolver< - Array, - ParentType, - ContextType, - RequireFields< - QueryDataCubeComponentTermsetsArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; - dataCubeMetadata?: Resolver< - ResolversTypes["DataCubeMetadata"], - ParentType, - ContextType, - RequireFields< - QueryDataCubeMetadataArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; - dataCubeObservations?: Resolver< - ResolversTypes["DataCubeObservations"], - ParentType, - ContextType, - RequireFields< - QueryDataCubeObservationsArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; - dataCubePreview?: Resolver< - ResolversTypes["DataCubePreview"], - ParentType, - ContextType, - RequireFields< - QueryDataCubePreviewArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; - possibleFilters?: Resolver< - Array, - ParentType, - ContextType, - RequireFields< - QueryPossibleFiltersArgs, - "sourceType" | "sourceUrl" | "cubeFilter" - > - >; - searchCubes?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - dataCubeDimensionGeoShapes?: Resolver< - Maybe, - ParentType, - ContextType, - RequireFields< - QueryDataCubeDimensionGeoShapesArgs, - "sourceType" | "sourceUrl" | "locale" | "cubeFilter" - > - >; +export type PossibleFilterValueResolvers = ResolversObject<{ + type?: Resolver; + id?: Resolver; + value?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type QueryResolvers = ResolversObject<{ + dataCubeLatestIri?: Resolver>; + dataCubeUnversionedIri?: Resolver, ParentType, ContextType, RequireFields>; + dataCubeComponents?: Resolver>; + dataCubeComponentTermsets?: Resolver, ParentType, ContextType, RequireFields>; + dataCubeMetadata?: Resolver>; + dataCubeObservations?: Resolver>; + dataCubeObservationsPaginated?: Resolver>; + dataCubePreview?: Resolver>; + possibleFilters?: Resolver, ParentType, ContextType, RequireFields>; + searchCubes?: Resolver, ParentType, ContextType, RequireFields>; + dataCubeDimensionGeoShapes?: Resolver, ParentType, ContextType, RequireFields>; }>; -export interface RawObservationScalarConfig - extends GraphQLScalarTypeConfig { - name: "RawObservation"; +export interface RawObservationScalarConfig extends GraphQLScalarTypeConfig { + name: 'RawObservation'; } -export type RelatedDimensionResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["RelatedDimension"] = ResolversParentTypes["RelatedDimension"], -> = ResolversObject<{ - type?: Resolver< - ResolversTypes["RelatedDimensionType"], - ParentType, - ContextType - >; - id?: Resolver; +export type RelatedDimensionResolvers = ResolversObject<{ + type?: Resolver; + id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export interface SearchCubeScalarConfig - extends GraphQLScalarTypeConfig { - name: "SearchCube"; +export interface SearchCubeScalarConfig extends GraphQLScalarTypeConfig { + name: 'SearchCube'; } -export type SearchCubeResultResolvers< - ContextType = VisualizeGraphQLContext, - ParentType extends - ResolversParentTypes["SearchCubeResult"] = ResolversParentTypes["SearchCubeResult"], -> = ResolversObject<{ - score?: Resolver, ParentType, ContextType>; - cube?: Resolver; - highlightedTitle?: Resolver< - Maybe, - ParentType, - ContextType - >; - highlightedDescription?: Resolver< - Maybe, - ParentType, - ContextType - >; +export type SearchCubeResultResolvers = ResolversObject<{ + score?: Resolver, ParentType, ContextType>; + cube?: Resolver; + highlightedTitle?: Resolver, ParentType, ContextType>; + highlightedDescription?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export interface SingleFiltersScalarConfig - extends GraphQLScalarTypeConfig { - name: "SingleFilters"; +export interface SingleFiltersScalarConfig extends GraphQLScalarTypeConfig { + name: 'SingleFilters'; } -export interface TermsetScalarConfig - extends GraphQLScalarTypeConfig { - name: "Termset"; +export interface TermsetScalarConfig extends GraphQLScalarTypeConfig { + name: 'Termset'; } -export interface ValueIdentifierScalarConfig - extends GraphQLScalarTypeConfig { - name: "ValueIdentifier"; +export interface ValueIdentifierScalarConfig extends GraphQLScalarTypeConfig { + name: 'ValueIdentifier'; } -export interface ValuePositionScalarConfig - extends GraphQLScalarTypeConfig { - name: "ValuePosition"; +export interface ValuePositionScalarConfig extends GraphQLScalarTypeConfig { + name: 'ValuePosition'; } export type Resolvers = ResolversObject<{ @@ -751,6 +644,7 @@ export type Resolvers = ResolversObject<{ DataCubeComponents?: GraphQLScalarType; DataCubeMetadata?: GraphQLScalarType; DataCubeObservations?: GraphQLScalarType; + DataCubeObservationsPage?: DataCubeObservationsPageResolvers; DataCubeOrganization?: DataCubeOrganizationResolvers; DataCubePreview?: GraphQLScalarType; DataCubeTermset?: DataCubeTermsetResolvers; @@ -762,6 +656,7 @@ export type Resolvers = ResolversObject<{ GeoShapes?: GraphQLScalarType; HierarchyValue?: GraphQLScalarType; Observation?: GraphQLScalarType; + PaginationInfo?: PaginationInfoResolvers; PossibleFilterValue?: PossibleFilterValueResolvers; Query?: QueryResolvers; RawObservation?: GraphQLScalarType; @@ -774,20 +669,19 @@ export type Resolvers = ResolversObject<{ ValuePosition?: GraphQLScalarType; }>; + /** * @deprecated * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. */ -export type IResolvers = - Resolvers; -export type DirectiveResolvers = - ResolversObject<{ - safeUrl?: SafeUrlDirectiveResolver; - }>; +export type IResolvers = Resolvers; +export type DirectiveResolvers = ResolversObject<{ + safeUrl?: SafeUrlDirectiveResolver; +}>; + /** * @deprecated * Use "DirectiveResolvers" root object instead. If you wish to get "IDirectiveResolvers", add "typesPrefix: I" to your config. */ -export type IDirectiveResolvers = - DirectiveResolvers; +export type IDirectiveResolvers = DirectiveResolvers; \ No newline at end of file diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index ba7f02f01b..fd5a5c23e7 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -43,6 +43,10 @@ export const Query: QueryResolvers = { const source = getSource(args.sourceType); return await source.dataCubeObservations(parent, args, context, info); }, + dataCubeObservationsPaginated: async (parent, args, context, info) => { + const source = getSource(args.sourceType); + return await source.dataCubeObservationsPaginated(parent, args, context, info); + }, dataCubePreview: async (parent, args, context, info) => { const source = getSource(args.sourceType); return await source.dataCubePreview(parent, args, context, info); diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts index 37a2956991..6353162cbd 100644 --- a/app/graphql/resolvers/rdf.ts +++ b/app/graphql/resolvers/rdf.ts @@ -38,6 +38,7 @@ import { createCubeDimensionValuesLoader, getCubeDimensions, getCubeObservations, + getCubeObservationsCount, } from "@/rdf/queries"; import { queryCubeUnversionedIri } from "@/rdf/query-cube-unversioned-iri"; import { GeoShape } from "@/rdf/query-geo-shapes"; @@ -467,6 +468,79 @@ export const dataCubeObservations: NonNullable< }; }; +export const dataCubeObservationsPaginated: NonNullable< + QueryResolvers["dataCubeObservationsPaginated"] +> = async (_, { locale, cubeFilter }, { setup }, info) => { + const { loaders, sparqlClient, cache } = await setup(info); + const { iri, filters: _filters, componentIds, limit, offset, orderBy } = cubeFilter; + const cube = await loaders.cube.load(iri); + + if (!cube) { + throw Error("Cube not found!"); + } + + await cube.fetchShape(); + + const filters = _filters ? getFiltersByComponentIris(_filters) : undefined; + const componentIris = componentIds?.map( + (id) => parseComponentId(id as ComponentId).unversionedComponentIri ?? id + ); + + const [{ query, observations }, totalCount] = await Promise.all([ + getCubeObservations({ + cube, + locale, + sparqlClient, + filters, + limit, + offset, + orderBy, + componentIris, + cache, + }), + getCubeObservationsCount({ + cube, + locale, + sparqlClient, + filters, + componentIris, + cache, + }), + ]); + + const actualLimit = limit ?? totalCount; + const actualOffset = offset ?? 0; + const hasNextPage = actualOffset + actualLimit < totalCount; + const hasPreviousPage = actualOffset > 0; + + return { + data: { + data: observations, + sparqlEditorUrl: getSparqlEditorUrl({ + query, + dataSource: { + type: info.variableValues.sourceType, + url: info.variableValues.sourceUrl, + }, + }), + }, + pagination: { + hasNextPage, + hasPreviousPage, + totalCount, + limit: actualLimit, + offset: actualOffset, + }, + sparqlEditorUrl: getSparqlEditorUrl({ + query, + dataSource: { + type: info.variableValues.sourceType, + url: info.variableValues.sourceUrl, + }, + }), + }; +}; + export const dataCubePreview: NonNullable< QueryResolvers["dataCubePreview"] > = async (_, { locale, cubeFilter }, { setup }, info) => { diff --git a/app/graphql/resolvers/sql.ts b/app/graphql/resolvers/sql.ts index 5ee5b6757b..f047a37b60 100644 --- a/app/graphql/resolvers/sql.ts +++ b/app/graphql/resolvers/sql.ts @@ -220,6 +220,25 @@ export const dataCubeObservations: NonNullable< }; }; +export const dataCubeObservationsPaginated: NonNullable< + QueryResolvers["dataCubeObservationsPaginated"] +> = async () => { + return { + data: { + data: [], + sparqlEditorUrl: "", + }, + pagination: { + hasNextPage: false, + hasPreviousPage: false, + totalCount: 0, + limit: 0, + offset: 0, + }, + sparqlEditorUrl: "", + }; +}; + export const dataCubePreview: NonNullable< QueryResolvers["dataCubePreview"] > = async () => { diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 540bde8357..4efea12039 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -10,6 +10,20 @@ scalar HierarchyValue scalar DataCubeComponents scalar DataCubeMetadata scalar DataCubeObservations + +type PaginationInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + totalCount: Int + limit: Int + offset: Int +} + +type DataCubeObservationsPage { + data: DataCubeObservations! + pagination: PaginationInfo! + sparqlEditorUrl: String! +} scalar DataCubePreview scalar Termset scalar ComponentTermsets @@ -123,6 +137,19 @@ input DataCubeObservationFilter { filters: Filters componentIds: [String!] joinBy: [String!] + limit: Int + offset: Int + orderBy: [OrderByClause!] +} + +input OrderByClause { + componentId: String! + order: SortOrder! +} + +enum SortOrder { + ASC + DESC } input DataCubeLatestIriFilter { @@ -186,6 +213,12 @@ type Query { locale: String! cubeFilter: DataCubeObservationFilter! ): DataCubeObservations! + dataCubeObservationsPaginated( + sourceType: String! + sourceUrl: DataSourceUrl! + locale: String! + cubeFilter: DataCubeObservationFilter! + ): DataCubeObservationsPage! dataCubePreview( sourceType: String! sourceUrl: DataSourceUrl! diff --git a/app/rdf/queries.ts b/app/rdf/queries.ts index 4153f89fac..6e0cc391b8 100644 --- a/app/rdf/queries.ts +++ b/app/rdf/queries.ts @@ -352,6 +352,8 @@ export const getCubeObservations = async ({ filters, preview, limit, + offset, + orderBy, raw, componentIris, cache, @@ -367,6 +369,10 @@ export const getCubeObservations = async ({ preview?: boolean | null; /** Limit on the number of observations returned */ limit?: number | null; + /** Offset for pagination */ + offset?: number | null; + /** Order by clauses for sorting */ + orderBy?: Array<{ componentId: string; order: "ASC" | "DESC" }> | null; /** Returns IRIs instead of labels for NamedNodes */ raw?: boolean; componentIris?: Maybe; @@ -452,6 +458,8 @@ export const getCubeObservations = async ({ const { query, observationsRaw } = await fetchViewObservations({ preview, limit, + offset, + orderBy, observationsView, disableDistinct: !filters || Object.keys(filters).length === 0, }); @@ -485,6 +493,119 @@ export const getCubeObservations = async ({ }; }; +export const getCubeObservationsCount = async ({ + cube, + locale, + sparqlClient, + filters, + componentIris, + cache, +}: { + cube: ExtendedCube; + locale: string; + sparqlClient: ParsingClient; + filters?: Filters | null; + componentIris?: Maybe; + cache: LRUCache | undefined; +}): Promise => { + const cubeIri = cube.term?.value!; + const cubeView = View.fromCube(cube, false); + const unversionedCubeIri = + (await queryCubeUnversionedIri(sparqlClient, cubeIri)) ?? cubeIri; + const allResolvedDimensions = await getCubeDimensions({ + cube, + locale, + sparqlClient, + unversionedCubeIri, + cache, + }); + const resolvedDimensions = allResolvedDimensions.filter((d) => { + if (componentIris) { + return ( + componentIris.includes(d.data.iri) || + d.data.related.find((r) => componentIris?.includes(r.iri)) + ); + } + + return true; + }); + + const serverFilters: Record = {}; + let dbFilters: typeof filters = {}; + + for (const [k, v] of Object.entries(filters ?? {})) { + if (v.type !== "multi") { + dbFilters[k] = v; + } else { + const count = Object.keys(v.values).length; + if (count > 100) { + serverFilters[k] = v; + } else { + dbFilters[k] = v; + } + } + } + + const observationFilters = filters + ? await buildFilters({ + cube, + view: cubeView, + filters: dbFilters, + locale, + sparqlClient, + cache, + }) + : []; + + const observationDimensions = buildDimensions({ + cubeView, + dimensionIris: componentIris + ? componentIris.map( + (id) => + parseComponentId(id as ComponentId).unversionedComponentIri ?? id + ) + : componentIris, + resolvedDimensions, + cube, + locale, + observationFilters, + raw: true, + }); + + const observationsView = new View({ + dimensions: observationDimensions, + filters: observationFilters, + }); + + try { + const countQuery = observationsView.observationsQuery({ + disableDistinct: !filters || Object.keys(filters).length === 0, + }); + + // Create a COUNT query by modifying the original query + const originalQueryString = countQuery.query.toString(); + const whereMatch = originalQueryString.match(/WHERE\s*{[\s\S]*}/i); + if (!whereMatch) { + return 0; + } + + const whereClause = whereMatch[0]; + const countQueryString = `SELECT (COUNT(*) as ?count) ${whereClause}`; + const query = pragmas.concat(countQueryString).toString(); + + const result = await sparqlClient.query.select(query); + + if (result.length > 0 && result[0].count) { + return parseInt(result[0].count.value, 10); + } + + return 0; + } catch (e) { + console.warn("Count query failed, falling back to 0", e); + return 0; + } +}; + const makeServerFilter = ( filters: Record, resolvedDimensionsByIri: Record @@ -722,30 +843,82 @@ type ObservationRaw = Record; async function fetchViewObservations({ preview, limit, + offset, + orderBy, observationsView, disableDistinct, }: { preview?: boolean | null; limit?: number | null; + offset?: number | null; + orderBy?: Array<{ componentId: string; order: "ASC" | "DESC" }> | null; observationsView: View; disableDistinct: boolean; }) { /** - * Add LIMIT to query + * Add LIMIT and OFFSET to query */ - if (!preview && limit !== undefined) { + if (!preview && (limit !== undefined || offset !== undefined)) { // From https://github.com/zazuko/cube-creator/blob/a32a90ff93b2c6c1c5ab8fd110a9032a8d179670/apis/core/lib/domain/observations/lib/index.ts#L41 - observationsView.ptr.addOut(ns.cubeView.projection, (projection: $FixMe) => - projection.addOut(ns.cubeView.limit, limit) + observationsView.ptr.addOut( + ns.cubeView.projection, + (projection: $FixMe) => { + if (limit !== undefined) { + projection.addOut(ns.cubeView.limit, limit); + } + if (offset !== undefined) { + projection.addOut(ns.cubeView.offset, offset); + } + } ); } const fullQuery = observationsView.observationsQuery({ disableDistinct }); - const query = pragmas - .concat( - preview && limit ? fullQuery.previewQuery({ limit }) : fullQuery.query - ) - .toString(); + let queryString = ( + preview && limit ? fullQuery.previewQuery({ limit }) : fullQuery.query + ).toString(); + + // Add ORDER BY if specified + if (orderBy && orderBy.length > 0 && !preview) { + const orderClauses = orderBy + .map(({ componentId, order }) => { + // Find the corresponding dimension in the observationsView + const dimension = observationsView.dimensions.find((dim) => + dim.cubeDimensions.some((cd) => { + return ( + cd.path?.value === + parseComponentId(componentId as ComponentId) + .unversionedComponentIri + ); + }) + ); + + if (!dimension) { + console.warn( + `Could not find dimension for component ID: ${componentId}` + ); + return ""; + } + + // Get the SPARQL variable name for this dimension + // Find the variable by looking at the dimension's index in the dimensions array + const dimensionIndex = observationsView.dimensions.indexOf(dimension); + const variable = `?dimension${dimensionIndex}`; + return `${order}(${variable})`; + }) + .filter(Boolean) + .join(" "); + + if (orderClauses) { + // Insert ORDER BY before LIMIT/OFFSET + queryString = queryString.replace( + /(\s+LIMIT\s|\s+OFFSET\s|$)/i, + ` ORDER BY ${orderClauses}$1` + ); + } + } + + const query = pragmas.concat(queryString).toString(); let observationsRaw: PromiseValue | undefined;