Skip to content

Commit

Permalink
feat(ui): pager for shared dimensions
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Dec 30, 2024
1 parent 683ef80 commit 15a8d6b
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 44 deletions.
24 changes: 15 additions & 9 deletions apis/shared-dimensions/lib/domain/shared-dimensions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import path from 'path'
import type { Quad, Stream, Term, Literal } from '@rdfjs/types'
import type { Quad, Stream, Term, Literal, NamedNode } from '@rdfjs/types'
import { hydra, rdf, schema, sh } from '@tpluscode/rdf-ns-builders'
import $rdf from 'rdf-ext'
import { toRdf } from 'rdf-literal'
import { toRdf, fromRdf } from 'rdf-literal'
import { fromFile } from 'rdf-utils-fs'
import clownface from 'clownface'
import { isResource } from 'is-graph-pointer'
Expand All @@ -21,10 +21,11 @@ interface GetSharedDimensions {
includeDeprecated?: Literal
}

export async function getSharedDimensions(client: StreamClient, { freetextQuery = '', limit = 10, offset = 0, includeDeprecated }: GetSharedDimensions = {}): Promise<CollectionData<Quad[]>> {
export async function getSharedDimensions(client: StreamClient, { freetextQuery = '', limit = 10, offset = 0, includeDeprecated }: GetSharedDimensions = {}): Promise<CollectionData<Iterable<Quad>>> {
const { constructQuery } = await shapeToQuery()

const shape = await loadShape('dimensions-query-shape')
const memberQueryShape = await loadShape('dimensions-query-shape', md.MembersQueryShape)
const totalQueryShape = await loadShape('dimensions-query-shape', md.CountQueryShape)

const { MANAGED_DIMENSIONS_BASE } = env
const variables = new Map(Object.entries({
Expand All @@ -35,19 +36,24 @@ export async function getSharedDimensions(client: StreamClient, { freetextQuery
includeDeprecated,
orderBy: schema.name,
}))
await rewriteTemplates(shape, variables)
await rewriteTemplates(memberQueryShape, variables)
await rewriteTemplates(totalQueryShape, variables)

const dataset = await $rdf.dataset().import(await client.query.construct(constructQuery(shape)))
const dataset = await $rdf.dataset().import(await client.query.construct(constructQuery(memberQueryShape)))
clownface({ dataset })
.has(rdf.type, schema.DefinedTermSet)
.forEach(termSet => {
termSet.addOut(md.export, $rdf.namedNode(`${MANAGED_DIMENSIONS_BASE}dimension/_export?dimension=${termSet.value}`))
termSet.addOut(md.terms, $rdf.namedNode(`${MANAGED_DIMENSIONS_BASE}dimension/_terms?dimension=${termSet.value}`))
})

const totalItems = clownface({
dataset: await $rdf.dataset().import(await client.query.construct(constructQuery(totalQueryShape))),
}).has(hydra.totalItems).out(hydra.totalItems).term as Literal

return {
members: dataset,
totalItems: dataset.match(null, rdf.type, schema.DefinedTermSet).length,
totalItems: fromRdf(totalItems),
}
}

Expand Down Expand Up @@ -97,12 +103,12 @@ export async function getSharedTerms<C extends StreamClient | ParsingClient>({ s
}) as any
}

async function loadShape(shape: string) {
async function loadShape(shape: string, shapeType: NamedNode = sh.NodeShape) {
const dataset = await $rdf.dataset().import(fromFile(path.resolve(__dirname, `../shapes/${shape}.ttl`)))

const ptr = clownface({
dataset,
}).has(rdf.type, sh.NodeShape)
}).has(rdf.type, shapeType)

if (!isResource(ptr)) {
throw new Error('Expected a single blank node or named node')
Expand Down
22 changes: 21 additions & 1 deletion apis/shared-dimensions/lib/shapes/dimensions-query-shape.ttl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PREFIX hydra: <http://www.w3.org/ns/hydra/core#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
Expand All @@ -8,7 +9,26 @@ PREFIX sparql: <http://datashapes.org/sparql#>
prefix md: <https://cube-creator.zazuko.com/shared-dimensions/vocab#>

[
a sh:NodeShape ;
a sh:NodeShape, md:CountQueryShape ;
sh:rule
[
sh:subject hydra:Collection ;
sh:predicate hydra:totalItems ;
sh:object
[
sh:count
[
sh:distinct
[
sh:filterShape _:FilterShape ;
] ;
]
] ;
] ;
] .

[
a sh:NodeShape, md:MembersQueryShape ;
sh:target
[
a s2q:NodeExpressionTarget ;
Expand Down
2 changes: 1 addition & 1 deletion apis/shared-dimensions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@cube-creator/core": "1.0.0",
"@cube-creator/express": "0.0.0",
"@hydrofoil/labyrinth": "^0.4.2",
"@hydrofoil/shape-to-query": "^0.13.2",
"@hydrofoil/shape-to-query": "^0.13.4",
"@rdfine/hydra": "^0.8.2",
"@rdfine/rdfs": "^0.6.4",
"@rdfine/schema": "^0.6.3",
Expand Down
4 changes: 3 additions & 1 deletion packages/core/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ type SharedDimensionsTerms =
'Hierarchies' |
'Hierarchy' |
'Entrypoint' |
'FreeTextSearchConstraintComponent'
'FreeTextSearchConstraintComponent' |
'MembersQueryShape' |
'CountQueryShape'

prefixes.view = 'https://cube.link/view/'

Expand Down
2 changes: 1 addition & 1 deletion packages/model/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export interface ImportProject extends Project {

export type CubeProject = CsvProject | ImportProject

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ProjectsCollection extends Collection<CsvProject | ImportProject> {
searchParams: GraphPointer
}

export const isCsvProject = (project: CsvProject | ImportProject): project is CsvProject => {
Expand Down
18 changes: 16 additions & 2 deletions ui/src/store/modules/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ActionTree, MutationTree, GetterTree } from 'vuex'
import { api } from '@/api'
import { RootState } from '../types'
import * as ns from '@cube-creator/core/namespace'
import { Project, ProjectsCollection } from '@cube-creator/model'
import { CubeProject, Project, ProjectsCollection } from '@cube-creator/model'
import { serializeProjectDetails, serializeCollection } from '../serializers'
import { RdfResource } from 'alcaeus'

Expand Down Expand Up @@ -53,7 +53,7 @@ const actions: ActionTree<ProjectsState, RootState> = {

const mutations: MutationTree<ProjectsState> = {
storeCollection (state, collection) {
state.collection = collection ? serializeCollection(collection) : null
state.collection = collection ? serializeCollection(collection, sortProject) : null
},

storeProjectDetails (state, { project, details }) {
Expand All @@ -64,6 +64,20 @@ const mutations: MutationTree<ProjectsState> = {
},
}

function sortProject (a: CubeProject, b: CubeProject) {
const aPlannedUpdate = a.plannedNextUpdate?.toISOString()
if (!aPlannedUpdate) {
return 1
}

const bPlannedUpdate = b.plannedNextUpdate?.toISOString()
if (!bPlannedUpdate) {
return -1
}

return aPlannedUpdate.localeCompare(bPlannedUpdate) || a.label.localeCompare(b.label)
}

export default {
namespaced: true,
state: initialState,
Expand Down
7 changes: 6 additions & 1 deletion ui/src/store/modules/sharedDimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RootState } from '../types'
import { cc, md } from '@cube-creator/core/namespace'
import { Collection, RdfResource } from 'alcaeus'
import { serializeCollection } from '@/store/serializers'
import { schema } from '@tpluscode/rdf-ns-builders'

export interface SharedDimensionsState {
entrypoint: null | RdfResource
Expand Down Expand Up @@ -63,10 +64,14 @@ const mutations: MutationTree<SharedDimensionsState> = {
},

storeCollection (state, collection) {
state.collection = collection ? serializeCollection(collection) : null
state.collection = collection ? serializeCollection(collection, sortByName) : null
},
}

function sortByName (l: RdfResource, r: RdfResource) {
return l.pointer.out(schema.name).value?.localeCompare(r.pointer.out(schema.name).value || '') || 0
}

export default {
namespaced: true,
state: initialState,
Expand Down
36 changes: 17 additions & 19 deletions ui/src/store/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,38 @@ import {
DimensionMetadata,
DimensionMetadataCollection,
JobCollection,
ProjectsCollection,
SourcesCollection,
Table,
TableCollection,
} from '@cube-creator/model'
import { IdentifierMapping, LiteralColumnMapping, ReferenceColumnMapping } from '@cube-creator/model/ColumnMapping'
import { Link } from '@cube-creator/model/lib/Link'
import { dcterms, oa, rdf, rdfs, schema } from '@tpluscode/rdf-ns-builders'
import { dcterms, hydra, oa, rdf, rdfs, schema } from '@tpluscode/rdf-ns-builders'
import { RdfResource, ResourceIdentifier } from '@tpluscode/rdfine/RdfResource'
import { ProjectDetails, SharedDimensionTerm } from './types'
import { clone } from '@/store/searchParams'
import type { Collection } from '@rdfine/hydra'
import type { GraphPointer } from 'clownface'

export const displayLanguage = ['en', 'de', 'fr', '']

export function serializeCollection (collection: ProjectsCollection): ProjectsCollection {
return Object.freeze({
...serializeResource(collection),
searchParams: clone(collection.pointer),
member: collection.member.sort(sortProject),
}) as ProjectsCollection
}

function sortProject (a: CubeProject, b: CubeProject) {
const aPlannedUpdate = a.plannedNextUpdate?.toISOString()
if (!aPlannedUpdate) {
return 1
declare module '@rdfine/hydra' {
interface Collection {
searchParams: GraphPointer
pageSize: number
perPage: number
}
}

const bPlannedUpdate = b.plannedNextUpdate?.toISOString()
if (!bPlannedUpdate) {
return -1
}
export function serializeCollection <T extends RdfResource> (collection: Collection<T>, sort: (l: T, r: T) => number): Collection<T> {
const member = sort ? collection.member.sort(sort) : collection.member

return aPlannedUpdate.localeCompare(bPlannedUpdate) || a.label.localeCompare(b.label)
return Object.freeze({
...serializeResource(collection),
searchParams: clone(collection.pointer),
member,
totalItems: collection.totalItems,
}) as Collection<T>
}

export function serializeProjectDetails (details: RdfResource): ProjectDetails {
Expand Down
46 changes: 41 additions & 5 deletions ui/src/views/SharedDimensions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@
</div>
<shared-dimension-tags :dimension="dimension" />
</router-link>
<o-pagination simple total="20" per-page="20" />
<o-pagination
simple
:current="page"
:total="collection.totalItems"
:per-page="pageSize"
@change="fetchPage"
/>
</div>
<p v-else class="has-text-grey">
No shared dimension yet
Expand All @@ -76,6 +82,7 @@ import TermWithLanguage from '@/components/TermWithLanguage.vue'
import { SharedDimension } from '@/store/types'
import { useHydraForm } from '@/use-hydra-form'
import { getRouteSearchParamsFromTemplatedOperation } from '@/router'
import { hydra } from '@tpluscode/rdf-ns-builders'
export default defineComponent({
name: 'CubeProjectsView',
Expand All @@ -94,6 +101,8 @@ export default defineComponent({
this.operation = this.collection.actions.get
this.searchParams = this.collection.searchParams
this.pageSize = this.searchParams.out(hydra.limit).value
this.page = parseInt(this.$router.currentRoute.value.query.page) || 1
},
setup () {
Expand All @@ -103,6 +112,8 @@ export default defineComponent({
return {
...form,
page: 1,
pageSize: 0,
searchParams: shallowRef()
}
},
Expand All @@ -122,6 +133,8 @@ export default defineComponent({
async beforeRouteUpdate (to) {
await this.$store.dispatch('sharedDimensions/fetchCollection', to.query)
this.searchParams = this.collection.searchParams
this.pageSize = this.searchParams.out(hydra.limit).value
this.page = parseInt(to.query.page) || 1
},
methods: {
Expand All @@ -131,11 +144,34 @@ export default defineComponent({
onSearch (e: CustomEvent) {
if (this.operation && e.detail?.value) {
this.$router.push({
query: getRouteSearchParamsFromTemplatedOperation(this.operation, e.detail?.value),
})
this.page = 1
const query = {
...getRouteSearchParamsFromTemplatedOperation(this.operation, e.detail?.value),
page: 1
}
this.$router.push({ query })
}
}
},
nextPage () {
this.fetchPage(this.page + 1)
},
prevPage () {
this.fetchPage(this.page - 1)
},
fetchPage (page: number) {
if (this.operation) {
this.page = page
const query = {
...getRouteSearchParamsFromTemplatedOperation(this.operation, this.searchParams),
page
}
this.$router.push({ query, })
}
},
}
})
</script>
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1566,10 +1566,10 @@
rdf-loaders-registry "^0.2.0"
sparql-http-client "^2.2.2"

"@hydrofoil/shape-to-query@^0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@hydrofoil/shape-to-query/-/shape-to-query-0.13.2.tgz#475939fe9ec5a3c0bd316f6a32bfaf4caf32cf83"
integrity sha512-7w1wPexW0XDiF7WtRkQtqnZTcJvhYKW+2rNIbl9cqZqto3A/8aKT+Hm4OKM3WTOgmGDpyDwbtE/itu5LkjzoEA==
"@hydrofoil/shape-to-query@^0.13.4":
version "0.13.4"
resolved "https://registry.yarnpkg.com/@hydrofoil/shape-to-query/-/shape-to-query-0.13.4.tgz#61ac73a33a1c8a34f6652a284ec7e2fdf5101b12"
integrity sha512-Q9GDAacePZZQkwnOxOD43BlkWBNDKr+VR05l3B8Q99D9gKYZ5AeF8kNDdH3za9LzES9Hc3hs/g0R8sM0Rz8WiQ==
dependencies:
"@hydrofoil/sparql-processor" "^0.1.2"
"@tpluscode/rdf-ns-builders" ">=3.0.2"
Expand Down

0 comments on commit 15a8d6b

Please sign in to comment.