Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dataservices search page #516

Merged
merged 22 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions udata_front/theme/gouvfr/assets/js/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type PaginatedArray<T> = {
data: Array<T>;
next_page: string | null;
page: number;
page_size: number;
previous_page: string | null;
total: number;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { withActions } from '@storybook/addon-actions/decorator';
import type { Meta, StoryObj } from '@storybook/vue3';
import DataservicesSearch from './DataservicesSearch.vue';

const meta = {
title: 'Components/DataservicesSearch',
component: DataservicesSearch,
decorators: [withActions],
args: {},
} satisfies Meta<typeof DataservicesSearch>;

export default meta;

export const DataservicesSearchExample: StoryObj<typeof meta> = {
render: (args) => ({
components: { DataservicesSearch },
setup() {
return { args };
},
template: '<DataservicesSearch v-bind="args"/>',
}),
args: {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<template>
<form class="fr-pt-3v" @submit.prevent="search">
<div class="fr-grid-row fr-grid-row--middle justify-between" ref="searchRef" data-cy="search">
ThibaudDauce marked this conversation as resolved.
Show resolved Hide resolved
<section class="fr-search-bar fr-search-bar--lg w-100">
<label class="fr-label" :for="searchId">
{{ t("Search") }}
</label>
<input
:id="searchId"
type="search"
v-model="searchQuery"
ref="searchInput"
class="fr-input"
:aria-label="t('Search...')"
:placeholder="t('Search...')"
data-cy="search-input"
data-testid="search-input"
/>
<button class="fr-btn" type="submit">
{{ t('Search') }}
</button>
</section>
</div>
<div class="fr-grid-row fr-mt-1w fr-mt-md-5v">
<div class="fr-col-12 fr-col-md-4 fr-col-lg-3">
<nav class="fr-sidemenu" aria-labelledby="fr-sidemenu-title">
<div class="fr-sidemenu__inner">
<button
class="fr-sidemenu__btn fr-mt-1w"
hidden
aria-controls="fr-sidemenu-wrapper"
aria-expanded="false"
>
{{ t('Filter results') }}
</button>
<div class="fr-collapse" id="fr-sidemenu-wrapper">
<div class="fr-sidemenu__title fr-mb-3v" id="fr-sidemenu-title">{{ t('Filters') }}</div>
<div class="fr-grid-row fr-grid-row--gutters">
<div class="fr-col-12">
<MultiSelect
:placeholder="t('Organizations')"
:searchPlaceholder="t('Search an organization...')"
:allOption="t('All organizations')"
listUrl="/organizations/?sort=-followers"
suggestUrl="/organizations/suggest/"
entityUrl="/organizations/"
:values="organization"
@change="(value: string) => organization = value"
:isBlue="true"
/>
</div>
<div class="fr-col-12">
<div class="fr-select-group">
<label class="fr-label" :for="isRestrictedId">
{{ t("Access terms") }}
</label>
<select class="fr-select" :id="isRestrictedId" v-model="isRestricted">
ThibaudDauce marked this conversation as resolved.
Show resolved Hide resolved
<option :value="null" selected disabled hidden>Sélectionner une option</option>
ThibaudDauce marked this conversation as resolved.
Show resolved Hide resolved
<option :value="false">{{ t("Open APIs to everyone") }}</option>
<option :value="true">{{ t("Restricted access APIs") }}</option>
</select>
</div>
</div>
<div class="fr-col-12 fr-mb-3w text-align-center" v-if="hasFilters">
<button
class="fr-btn fr-btn--secondary fr-icon-close-circle-line fr-btn--icon-left justify-center w-100"
@click="resetFilters"
>
{{ t('Reset filters') }}
</button>
</div>
</div>
</div>
</div>
</nav>
</div>
<section class="fr-col-12 fr-col-md-8 fr-col-lg-9 fr-mt-2w fr-mt-md-0 search-results">
<div v-if="dataservices === null">
<Loader />
</div>
<div class="fr-grid-row fr-grid-row--gutters fr-grid-row--middle justify-between fr-pb-1w" v-if="dataservices !== null">
<p class="fr-col-auto fr-my-0" role="status">
{{ t("{count} API", dataservices.total) }}
</p>
</div>
<div v-if="dataservices !== null && dataservices.data.length">
<ul class="fr-mt-1w border-default-grey border-top relative z-2">
<li v-for="dataservice in dataservices.data" :key="dataservice.id">
<DataserviceCard :dataservice :dataserviceUrl="dataservice.self_web_url" />
</li>
</ul>
<Pagination
v-if="dataservices !== null && dataservices.total > dataservices.page_size"
:page="page"
:pageSize="dataservices.page_size"
:totalResults="dataservices.total"
@change="changePage"
class="fr-mt-2w"
/>
</div>
<div v-if="dataservices !== null && dataservices.data.length === 0" class="fr-mt-2w">
<ActionCard
:title="t('No result found for your search')"
:icon="franceWithMagnifyingGlassIcon"
type="primary"
>
<p class="fr-mt-1v fr-mb-3v">
{{ t("Try to reset filters to widen your search.") }}<br/>
{{ t("You can also give us more details with our feedback form.") }}
</p>
<template v-slot:actions>
<button @click="resetFilters" class="fr-btn fr-btn--secondary">
{{ t("Reset filters") }}
</button>
<a :href="data_search_feedback_form_url" class="fr-btn fr-btn--tertiary-no-outline fr-btn--icon-left fr-icon-lightbulb-line fr-ml-1w">
{{ t("Tell us what you are looking for") }}
</a>
</template>
</ActionCard>
</div>
</section>
</div>
</form>
</template>

<script setup lang="ts">
import { Dataservice, DataserviceCard, Pagination } from "@datagouv/components/ts";
import { ref, onMounted, computed, useId, useTemplateRef, watchEffect, watch } from "vue";
import { useI18n } from 'vue-i18n';
import Loader from "../dataset/loader.vue";
import MultiSelect from "../MultiSelect/MultiSelect.vue";
import ActionCard from "../Form/ActionCard/ActionCard.vue";
import { data_search_feedback_form_url } from "../../config";
import { api } from "../../plugins/api";
import franceWithMagnifyingGlassIcon from "../../../../templates/svg/illustrations/france_with_magnifying_glass.svg";
import { PaginatedArray } from "../../api/types";
import { refDebounced } from '@vueuse/core'

const { t } = useI18n();

const organization = ref<string | null>(null);
const isRestricted = ref<boolean | null>(null);
const isRestrictedId = useId();

const page = ref(1);

const hasFilters = computed(() => {
return organization.value || isRestricted.value !== null
});
const resetFilters = () => {
organization.value = '';
isRestricted.value = null;
}

const searchId = useId();
const searchQuery = ref('');
const searchQueryDebounced = refDebounced(searchQuery, 500);
const searchInput = useTemplateRef('searchInput');
ThibaudDauce marked this conversation as resolved.
Show resolved Hide resolved

const url = computed(() => {
const filters: { organization?: string, q?: string, is_restricted?: string, page?: string } = {}
if (organization.value) {
filters.organization = organization.value;
}
if (searchQueryDebounced.value) {
filters.q = searchQueryDebounced.value;
}
if (isRestricted.value !== null) {
filters.is_restricted = isRestricted.value.toString();
}
if (page.value && page.value !== 1) {
filters.page = page.value.toString();
}
const params = new URLSearchParams(filters);

let url = new URL(window.location.href);
url.search = params.toString();
window.history.pushState(null, "", url);

return `/dataservices?${params}`
})

const dataservices = ref<null | PaginatedArray<Dataservice>>(null);

const search = async () => {
dataservices.value = null
const response = await api.get(url.value);
ThibaudDauce marked this conversation as resolved.
Show resolved Hide resolved
dataservices.value = response.data;
};

watch(organization, () => {
page.value = 1;
})

onMounted(() => {
searchInput.value?.focus();

window.addEventListener('popstate', () => {
const url = new URL(window.location.href);

organization.value = url.searchParams.get('organization');
searchQuery.value = url.searchParams.get('q') || '';
page.value = parseInt(url.searchParams.get('page') || '1' );
});
})
watchEffect(() => {
search()
})

const changePage = (newPage: number) => {
page.value = newPage
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export default defineComponent({
},
content: {
type: String,
required: true,
},
type: {
type: String,
Expand Down
2 changes: 2 additions & 0 deletions udata_front/theme/gouvfr/assets/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import EventBus from "./plugins/eventbus.ts";
import Auth from "./plugins/auth.ts";
import InitSentry from "./sentry.ts";
import ReportModalButton from "./components/Report/ReportModalButton.vue";
import DataservicesSearch from "./components/DataservicesSearch/DataservicesSearch.vue";

setupComponents({
admin_root,
Expand Down Expand Up @@ -82,6 +83,7 @@ const configAndMountApp = (el: HTMLElement) => {
app.component("owner-type-icon", OwnerTypeIcon);
app.component("publishing-form", PublishingForm);
app.component("search", Search);
app.component("dataservices-search", DataservicesSearch);
app.component("toggletip", Toggletip);
app.component("user-dataset-list", UserDatasetList);
app.component("user-reuse-list", UserReuseList);
Expand Down
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Member since",
"Last connection": "Last connection",
"No connection": "No connection",
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset"
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset",
"Access terms": "",
"Open APIs to everyone": "",
"Restricted access APIs": "",
"{count} API": ""
}
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Member since",
"Last connection": "Last connection",
"No connection": "No connection",
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset"
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset",
"Access terms": "Access terms",
"Open APIs to everyone": "Open APIs to everyone",
"Restricted access APIs": "Restricted access APIs",
"{count} API": "{count} API"
nicolaskempf57 marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Member since",
"Last connection": "Last connection",
"No connection": "No connection",
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset"
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset",
"Access terms": "",
"Open APIs to everyone": "",
"Restricted access APIs": "",
"{count} API": ""
}
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Membre depuis",
"Last connection": "Dernière connexion",
"No connection": "Aucune connection",
"You are seeing a specific community resource of this dataset": "Vous consultez une ressource communautaire spécifique sur ce jeu de données"
"You are seeing a specific community resource of this dataset": "Vous consultez une ressource communautaire spécifique sur ce jeu de données",
"Access terms": "",
"Open APIs to everyone": "",
"Restricted access APIs": "",
"{count} API": ""
}
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Member since",
"Last connection": "Last connection",
"No connection": "No connection",
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset"
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset",
"Access terms": "",
"Open APIs to everyone": "",
"Restricted access APIs": "",
"{count} API": ""
}
6 changes: 5 additions & 1 deletion udata_front/theme/gouvfr/assets/js/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,9 @@
"Member since": "Member since",
"Last connection": "Last connection",
"No connection": "No connection",
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset"
"You are seeing a specific community resource of this dataset": "You are seeing a specific community resource of this dataset",
"Access terms": "",
"Open APIs to everyone": "",
"Restricted access APIs": "",
"{count} API": ""
}
Loading