Skip to content
Snippets Groups Projects
Commit 7fdde36d authored by Mathieu Massaviol's avatar Mathieu Massaviol
Browse files

WIP search page #29

parent 8c29001c
No related branches found
No related tags found
1 merge request!75Resolve "Permettre la recherche d'un jeu de données"
...@@ -49,7 +49,8 @@ const menuItems = computed(() => { ...@@ -49,7 +49,8 @@ const menuItems = computed(() => {
params: { params: {
...route.params, ...route.params,
lang: key lang: key
} },
query: route.query
}) })
} }
}) })
......
...@@ -5,16 +5,30 @@ ...@@ -5,16 +5,30 @@
> >
<div class="flex flex-row"> <div class="flex flex-row">
<div class="p-4 pl-8 w-2/12 ml-auto mr-auto"> <div class="p-4 pl-8 w-2/12 ml-auto mr-auto">
<Panel :header="t('search.resourceType')" toggleable> <form id="searchForm" method="get" ref="searchForm" v-on:submit="submit">
<template #toggleicon="toggleIconProps"> <Panel :header="t('search.resourceType')" toggleable>
<i v-if="toggleIconProps.collapsed" class="fa-solid fa-chevron-down text-gray-700"/> <template #toggleicon="toggleIconProps">
<i v-else class="fa-solid fa-chevron-up text-gray-700"/> <i v-if="toggleIconProps.collapsed" class="fa-solid fa-chevron-down text-gray-700" />
</template> <i v-else class="fa-solid fa-chevron-up text-gray-700" />
<div class="flex items-center gap-2" v-for="(dcatType, uri) in type2ResourceType" v-bind:key="uri"> </template>
<Checkbox v-model="resourceType" :inputId="uri" name="resourceType" :value="uri" /> <div
<label for="resourceType" class="capitalize"> {{ t('resourceType.'+dcatType) }} </label> class="flex items-center gap-2"
</div> v-for="(dcatType, uri) in type2ResourceType"
</Panel> v-bind:key="uri"
>
<Checkbox
v-model="resourceType"
:inputId="uri"
name="resourceType"
:value="uri"
@change="submit"
/>
<label for="resourceType" class="capitalize">
{{ t('resourceType.' + dcatType) }}
</label>
</div>
</Panel>
</form>
</div> </div>
<div class="p-4 pl-8 w-9/12 ml-auto mr-auto"> <div class="p-4 pl-8 w-9/12 ml-auto mr-auto">
<h1 class="font-title text-4xl uppercase font-bold mb-4 text-primary"> <h1 class="font-title text-4xl uppercase font-bold mb-4 text-primary">
...@@ -23,12 +37,13 @@ ...@@ -23,12 +37,13 @@
<IconField class="w-full mt-8 mb-4"> <IconField class="w-full mt-8 mb-4">
<InputText <InputText
id="search" id="search"
form="searchForm"
v-model="queryString" v-model="queryString"
fluid fluid
size="large" size="large"
:placeholder="t('community.homepage.searchBarPlaceholder')" :placeholder="t('community.homepage.searchBarPlaceholder')"
/> />
<InputIcon class="fa-solid fa-magnifying-glass" :onclick="search" /> <InputIcon class="fa-solid fa-magnifying-glass" :onclick="submit" />
</IconField> </IconField>
<div v-if="!searching"> <div v-if="!searching">
<template v-for="result in searchResultObjects" v-bind:key="result"> <template v-for="result in searchResultObjects" v-bind:key="result">
...@@ -44,11 +59,11 @@ ...@@ -44,11 +59,11 @@
</div> </div>
<Paginator <Paginator
class="mt-8" class="mt-8"
always-show="false" :always-show="false"
:rows="itemsPerPage" :rows="itemsPerPage"
v-model:first="start"
:total-records="searchResultList.length" :total-records="searchResultList.length"
v-bind:currentPage="currentPage" v-on:page="submit"
v-on:page="getResultsForPage($event.page)"
/> />
</div> </div>
</div> </div>
...@@ -62,7 +77,7 @@ import { useCommunityData } from '@/dataLoaders/community' ...@@ -62,7 +77,7 @@ import { useCommunityData } from '@/dataLoaders/community'
import type { OcBreadcrumbItem, OcSearchResult } from '@/declarations' import type { OcBreadcrumbItem, OcSearchResult } from '@/declarations'
import OcLayoutSimple from '@/layout/OcLayoutSimple/OcLayoutSimple.vue' import OcLayoutSimple from '@/layout/OcLayoutSimple/OcLayoutSimple.vue'
import { useAccountStore } from '@/stores/account' import { useAccountStore } from '@/stores/account'
import { computed, ref } from 'vue' import { computed, onBeforeMount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import IconField from 'primevue/iconfield' import IconField from 'primevue/iconfield'
import InputText from 'primevue/inputtext' import InputText from 'primevue/inputtext'
...@@ -74,6 +89,7 @@ import Paginator from 'primevue/paginator' ...@@ -74,6 +89,7 @@ import Paginator from 'primevue/paginator'
import Panel from 'primevue/panel' import Panel from 'primevue/panel'
import Checkbox from 'primevue/checkbox' import Checkbox from 'primevue/checkbox'
import { type2ResourceType } from '@/helpers/resourceType' import { type2ResourceType } from '@/helpers/resourceType'
import { useRoute, useRouter } from 'vue-router'
definePage({ definePage({
name: 'community.search', name: 'community.search',
...@@ -84,17 +100,20 @@ definePage({ ...@@ -84,17 +100,20 @@ definePage({
}) })
const accountStore = useAccountStore() const accountStore = useAccountStore()
const { t } = useI18n() const { locale, t } = useI18n()
const { translateValue } = useTranslateValue() const { translateValue } = useTranslateValue()
const { data: community } = useCommunityData() const { data: community } = useCommunityData()
const route = useRoute()
const router = useRouter()
const itemsPerPage = 5 const itemsPerPage = 5
const currentPage = ref(0) const start = ref(route.query.start ?? 0)
const queryString = ref('') const queryString = ref(route.query.q)
const resourceType = ref([]) const resourceType = ref(route.query.type)
const searchResultList = ref<string[]>([]) const searchResultList = ref<string[]>([])
const searchResultObjects = ref<OcSearchResult[]>([]) const searchResultObjects = ref<OcSearchResult[]>([])
...@@ -102,13 +121,28 @@ const searchResultObjects = ref<OcSearchResult[]>([]) ...@@ -102,13 +121,28 @@ const searchResultObjects = ref<OcSearchResult[]>([])
const searching = ref(false) const searching = ref(false)
const errorMessage = ref<string | null>(null) const errorMessage = ref<string | null>(null)
const submit = () => {
router.push({
name: "community.search",
params: {
lang: locale.value,
community: community.value.name
},
query: {
q: queryString.value,
type: resourceType.value,
start: start.value
}
})
}
const search = async () => { const search = async () => {
if (searching.value) { if (searching.value) {
return return
} }
searching.value = true searching.value = true
currentPage.value = 0 start.value = 0
try { try {
searchResultList.value = await searchResources( searchResultList.value = await searchResources(
{ {
...@@ -122,31 +156,28 @@ const search = async () => { ...@@ -122,31 +156,28 @@ const search = async () => {
errorMessage.value = (e as Error).message ?? 'Error' errorMessage.value = (e as Error).message ?? 'Error'
} }
try { getResultsForStart(start.value)
searchResultObjects.value = await getSearchResults(
searchResultList.value.slice(0, itemsPerPage),
accountStore.auth
)
} catch (e) {
console.error(e)
errorMessage.value = (e as Error).message ?? 'Error'
}
searching.value = false searching.value = false
} }
const getResultsForPage = async (pagenumber: number) => { const getResultsForStart = async (start: number) => {
searching.value = true const resourceUriList = searchResultList.value.slice(start, start + itemsPerPage)
try { if (resourceUriList.length){
searchResultObjects.value = await getSearchResults( searching.value = true
searchResultList.value.slice(pagenumber * itemsPerPage, (pagenumber + 1) * itemsPerPage), try {
accountStore.auth searchResultObjects.value = await getSearchResults(
) resourceUriList,
} catch (e) { accountStore.auth
console.error(e) )
errorMessage.value = (e as Error).message ?? 'Error' } catch (e) {
console.error(e)
errorMessage.value = (e as Error).message ?? 'Error'
}
searching.value = false
} else {
console.log("No results")
} }
searching.value = false
} }
const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [ const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [
...@@ -162,13 +193,37 @@ const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [ ...@@ -162,13 +193,37 @@ const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [
type: 'search' type: 'search'
} }
]) ])
watch(
() => route.query,
(newQuery, oldQuery) => {
if (newQuery.q != oldQuery.q || newQuery.type != oldQuery.type){
start.value = parseInt(newQuery.start) ?? 0
getResultsForStart(start.value)
}
else {
start.value = 0
search()
}
}
)
onBeforeMount(() => {
if (route.query.q || route.query.type){
queryString.value = route.query.q as string ?? ""
resourceType.value = route.query.type as string[] ?? []
start.value = parseInt(route.query.start as string) ?? 0
search()
}
})
</script> </script>
<style scoped> <style scoped>
:deep(.p-panel-header) { :deep(.p-panel-header) {
@apply bg-primary rounded-t text-white mb-2 @apply bg-primary rounded-t text-white mb-2;
} }
:deep(.p-panel){ :deep(.p-panel) {
@apply bg-gray-100 @apply bg-gray-100;
} }
</style> </style>
\ No newline at end of file
...@@ -22,15 +22,19 @@ ...@@ -22,15 +22,19 @@
{{ t('community.homepage.searchBarLegend') }} {{ t('community.homepage.searchBarLegend') }}
</h3> </h3>
<form v-on:submit="search">
<IconField class="w-full mt-4 mb-4"> <IconField class="w-full mt-4 mb-4">
<InputText <InputText
id="search" id="search"
fluid v-model="queryString"
size="large" fluid
:placeholder="t('community.homepage.searchBarPlaceholder')" size="large"
/> :placeholder="t('community.homepage.searchBarPlaceholder')"
<InputIcon class="fa-solid fa-magnifying-glass" /> />
</IconField> <InputIcon class="fa-solid fa-magnifying-glass" @click="search"/>
</IconField>
</form>
</section> </section>
<section <section
...@@ -71,7 +75,7 @@ ...@@ -71,7 +75,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref } from 'vue'
import { useAccountStore } from '@/stores/account' import { useAccountStore } from '@/stores/account'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useTranslateValue } from '@/composables/useTranslateValue' import { useTranslateValue } from '@/composables/useTranslateValue'
...@@ -85,6 +89,7 @@ import { getColor } from '@/helpers/communityColor' ...@@ -85,6 +89,7 @@ import { getColor } from '@/helpers/communityColor'
import { useCommunityData } from '@/dataLoaders/community' import { useCommunityData } from '@/dataLoaders/community'
import { useTreeStore } from '@/stores/tree' import { useTreeStore } from '@/stores/tree'
import { useAbility } from '@casl/vue' import { useAbility } from '@casl/vue'
import { useRouter } from 'vue-router'
definePage({ definePage({
name: 'community', name: 'community',
...@@ -98,9 +103,10 @@ treeStore.state.selectedNodeKey = undefined ...@@ -98,9 +103,10 @@ treeStore.state.selectedNodeKey = undefined
const { data: community } = useCommunityData() const { data: community } = useCommunityData()
const { t } = useI18n() const { locale, t } = useI18n()
const { translateValue } = useTranslateValue() const { translateValue } = useTranslateValue()
const { cannot } = useAbility() const { cannot } = useAbility()
const router = useRouter()
const account = useAccountStore() const account = useAccountStore()
...@@ -108,4 +114,24 @@ const title = computed(() => translateValue(community.value?.title)) ...@@ -108,4 +114,24 @@ const title = computed(() => translateValue(community.value?.title))
const abstract = computed(() => translateValue(community.value?.abstract)) const abstract = computed(() => translateValue(community.value?.abstract))
const logoUrl = computed(() => community.value?.logo ?? null) const logoUrl = computed(() => community.value?.logo ?? null)
const color = computed(() => getColor(community.value)) const color = computed(() => getColor(community.value))
const isMemberOfCurrentCommunity = computed(
() => community.value?.['@id'] && memberCommunitiesList.value.includes(community.value?.['@id'])
)
const queryString = ref('')
const search = () => {
router.push({
name: 'community.search',
params: {
lang: locale.value,
community: community.value.name,
},
query: {
q: queryString.value,
start: 0
}
})
}
</script> </script>
...@@ -7,34 +7,36 @@ import { resourceContext } from './resource' ...@@ -7,34 +7,36 @@ import { resourceContext } from './resource'
* Get results URI of a search query * Get results URI of a search query
*/ */
export const searchResources = async (query: OcSearchQuery, auth?: Credentials) => { export const searchResources = async (query: OcSearchQuery, auth?: Credentials) => {
let typeFilter = "" let typeFilter = ''
if (query.resourceType?.length){ if (query.resourceType?.length) {
typeFilter = "FILTER (?type = <" typeFilter = 'FILTER (?type = <'
typeFilter += query.resourceType.join('> || ?type = <') typeFilter += query.resourceType.join('> || ?type = <')
typeFilter += ">)" typeFilter += '>)'
} }
let queryFilter = "" let queryFilter = ''
if (query.queryString){ if (query.queryString) {
queryFilter = `FILTER regex(?o, "${query.queryString}", "i")` queryFilter = `FILTER regex(?o, "${query.queryString}", "i")`
} }
const res = await executeSparqlSelect( const res = await executeSparqlSelect(
` `
SELECT DISTINCT ?resource SELECT DISTINCT ?resource
WHERE { WHERE {
?resource rdf:type ?type. ?resource rdf:type ?type.
?resource dct:identifier ?identifier.
${typeFilter} ${typeFilter}
FILTER EXISTS { FILTER EXISTS {
VALUES ?p { dct:title dct:description } VALUES ?p { dct:title dct:description }
?resource ?p ?o ?resource ?p ?o
${queryFilter} ${queryFilter}
} }
} } ORDER BY ASC(?identifier)
`, `,
{ {
auth: auth, auth: auth
}) }
)
return res.map((result) => result.resource.value) return res.map((result) => result.resource.value)
} }
...@@ -42,7 +44,25 @@ export const searchResources = async (query: OcSearchQuery, auth?: Credentials) ...@@ -42,7 +44,25 @@ export const searchResources = async (query: OcSearchQuery, auth?: Credentials)
/** Get searchResults objects from URI list */ /** Get searchResults objects from URI list */
export const getSearchResults = async (resourceUriList: string[], auth?: Credentials) => { export const getSearchResults = async (resourceUriList: string[], auth?: Credentials) => {
const searchResultContext: ContextDefinition = { const searchResultContext: ContextDefinition = {
...resourceContext, '@type': {
'@container': '@set'
},
identifier: {
'@id': 'http://purl.org/dc/terms/identifier'
},
title: {
'@id': 'http://purl.org/dc/terms/title',
'@container': '@language'
},
description: {
'@id': 'http://purl.org/dc/terms/description',
'@container': '@language'
},
graph: {
'@id': 'https://www.irit.fr/opencommon/terms/graph',
'@type': '@id',
'@container': ['@set']
},
catalog: { catalog: {
'@id': 'http://www.w3.org/ns/dcat#catalog', '@id': 'http://www.w3.org/ns/dcat#catalog',
'@type': '@id', '@type': '@id',
...@@ -57,17 +77,17 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent ...@@ -57,17 +77,17 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent
'@id': 'http://www.w3.org/ns/dcat#distribution', '@id': 'http://www.w3.org/ns/dcat#distribution',
'@type': '@id', '@type': '@id',
'@container': '@set' '@container': '@set'
}, }
} }
const formattedUris = '<' + resourceUriList.join('> <') + '>' const formattedUris = '<' + resourceUriList.join('> <') + '>'
return await executeSparqlConstruct<OcSearchResult>( return await executeSparqlConstruct<OcSearchResult>(
` `
CONSTRUCT { CONSTRUCT {
?s ?p ?o. ?s ?p ?o.
?s oct:graph ?g. ?s oct:graph ?g.
?s dcat:dataset ?dataset ?s dct:identifier ?identifier.
} }
WHERE { WHERE {
VALUES ?s { ${formattedUris} } VALUES ?s { ${formattedUris} }
...@@ -88,8 +108,9 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent ...@@ -88,8 +108,9 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent
} }
} }
`, `,
{ {
context: searchResultContext, context: searchResultContext,
auth: auth, auth: auth
}) }
} )
\ No newline at end of file }
...@@ -30,7 +30,7 @@ declare module 'vue-router/auto-routes' { ...@@ -30,7 +30,7 @@ declare module 'vue-router/auto-routes' {
'community.dashboard.dataset': RouteRecordInfo<'community.dashboard.dataset', '/:lang/community/:community/dashboard/dataset', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.dashboard.dataset': RouteRecordInfo<'community.dashboard.dataset', '/:lang/community/:community/dashboard/dataset', { community: ParamValue<true> }, { community: ParamValue<false> }>,
'community.datasets.new': RouteRecordInfo<'community.datasets.new', '/:lang/community/:community/dataset/new', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.datasets.new': RouteRecordInfo<'community.datasets.new', '/:lang/community/:community/dataset/new', { community: ParamValue<true> }, { community: ParamValue<false> }>,
'community.join': RouteRecordInfo<'community.join', '/:lang/community/:community/join', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.join': RouteRecordInfo<'community.join', '/:lang/community/:community/join', { community: ParamValue<true> }, { community: ParamValue<false> }>,
'/:lang/community/[community].search': RouteRecordInfo<'/:lang/community/[community].search', '/:lang/community/:community/search', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.search': RouteRecordInfo<'community.search', '/:lang/community/:community/search', { community: ParamValue<true> }, { community: ParamValue<false> }>,
'connection': RouteRecordInfo<'connection', '/:lang/connection', Record<never, never>, Record<never, never>>, 'connection': RouteRecordInfo<'connection', '/:lang/connection', Record<never, never>, Record<never, never>>,
'logout': RouteRecordInfo<'logout', '/:lang/logout', Record<never, never>, Record<never, never>>, 'logout': RouteRecordInfo<'logout', '/:lang/logout', Record<never, never>, Record<never, never>>,
'profile': RouteRecordInfo<'profile', '/:lang/profile', Record<never, never>, Record<never, never>>, 'profile': RouteRecordInfo<'profile', '/:lang/profile', Record<never, never>, Record<never, never>>,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment