Skip to content

Commit 48cd33d

Browse files
ThibaudDauceclaudemaudetes
authored
fix: TypeScript errors and improve type safety (#787)
## Summary This PR fixes various TypeScript errors and improves type safety across multiple components. ## Changes ### Type annotations for implicit `any` parameters - `AdminMembershipRequest.vue`: Add type annotation for `close` callback parameter - `AdminCommunityResourcesTable.vue`: Add type annotations for `closeModal` and `resourceForm` in event handler ### Fix null/undefined type errors - `AdminContactPointsTable.vue`: Reset form to default values instead of `null` on modal close - `AdminDiscussionsTable.vue`: Add non-null assertions where `v-if` already guarantees non-null values - `AdminDataservicesPage.vue` / `AdminDatasetsPage.vue`: Add non-null assertions for `recipient` prop (guarded by `v-if`) ### Fix missing/incorrect types - `AdminDatasetsPage.vue`: - Add explicit type for query params object with optional `archived`, `deleted`, `private` properties - Remove assignment to read-only `refDebounced` value - `DatasetsMetrics.vue`: Add missing 4th argument (`dataservicesViews`) to `createOrganizationMetricsUrl` - `types/types.d.ts`: Export `Owned` type that was used but not exported - `utils/contacts.ts`: Use `as const` for `role` property to preserve literal type ### Improve sitemap route types - `sitemaps/pages.ts`: Add `GithubTreeResponse` interface for GitHub API response - `sitemaps/urls.ts`: - Add `TotalResponse` and `PaginatedResponse` interfaces - Add proper type annotation for `event` parameter - Fix type coercions for query parameters ## Test plan - [ ] Run `npm run type-check` to verify TypeScript errors are reduced - [ ] Test sitemap generation - [ ] Test admin pages for datasets, dataservices, discussions, and contact points 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: maudetes <estelle.maudet@data.gouv.fr>
1 parent 5c1986f commit 48cd33d

File tree

10 files changed

+78
-38
lines changed

10 files changed

+78
-38
lines changed

components/AdminMembershipRequest/AdminMembershipRequest.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const accept = async () => {
132132
133133
const refuseFormId = useId()
134134
const refuseComment = ref('')
135-
const refuse = async (close) => {
135+
const refuse = async (close: () => void) => {
136136
try {
137137
loading.value = true
138138
await $api(`/api/1/organizations/${props.oid}/membership/${props.request.id}/refuse`, {

components/AdminTable/AdminCommunityResourcesTable/AdminCommunityResourcesTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<FileEditModalFromQueryStringClient
33
:schemas
4-
@submit="(closeModal, resourceForm) => updateResource(resourceForm.resource, closeModal, resourceForm)"
4+
@submit="(closeModal: () => void, resourceForm: CommunityResourceForm) => updateResource(resourceForm.resource, closeModal, resourceForm)"
55
/>
66

77
<AdminTable>

components/AdminTable/AdminContactPointsTable/AdminContactPointsTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
:title="$t('Éditer le point de contact')"
5252
size="lg"
5353
@open="newContactForm = contactPoint"
54-
@close="newContactForm = null"
54+
@close="newContactForm = { ...defaultContactForm, id: '' }"
5555
>
5656
<template #button="{ attrs, listeners }">
5757
<BrandedButton

components/AdminTable/AdminDiscussionsTable/AdminDiscussionsTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
<p v-if="!subject && subjects[discussion.subject.id]">
6666
<a
6767
class="fr-link inline-flex"
68-
:href="getSubjectPage(subjects[discussion.subject.id])"
68+
:href="getSubjectPage(subjects[discussion.subject.id]!)"
6969
>
7070
<component
7171
:is="getSubjectTypeIcon(discussion.subject.class)"
@@ -74,7 +74,7 @@
7474
/>
7575
<TextClamp
7676
class="overflow-wrap-anywhere"
77-
:text="getSubjectTitle(subjects[discussion.subject.id])"
77+
:text="getSubjectTitle(subjects[discussion.subject.id]!)"
7878
:auto-resize="true"
7979
:max-lines="1"
8080
/>

components/Dataservices/AdminDataservicesPage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<TransferRequestList
1111
v-if="props.organization || props.user"
1212
type="Dataservice"
13-
:recipient="props.organization || props.user"
13+
:recipient="(props.organization || props.user)!"
1414
@done="refresh"
1515
/>
1616
<div

components/Datasets/AdminDatasetsPage.vue

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<TransferRequestList
1212
v-if="props.organization || props.user"
1313
type="Dataset"
14-
:recipient="props.organization || props.user"
14+
:recipient="(props.organization || props.user)!"
1515
@done="refresh"
1616
/>
1717

@@ -161,12 +161,21 @@ function sort(column: DatasetSortedBy, newDirection: SortDirection) {
161161
162162
function resetFilters() {
163163
q.value = ''
164-
qDebounced.value = ''
165164
datasetsStatus.value = null
166165
}
167166
168167
const params = computed(() => {
169-
const query = {
168+
const query: {
169+
organization: string | undefined
170+
owner: string | undefined
171+
sort: string
172+
q: string
173+
page_size: number
174+
page: number
175+
archived?: string
176+
deleted?: string
177+
private?: string
178+
} = {
170179
organization: props.organization?.id,
171180
owner: props.user?.id,
172181
@@ -178,18 +187,18 @@ const params = computed(() => {
178187
179188
if (datasetsStatus.value) {
180189
if (datasetsStatus.value.id === 'public') {
181-
query['archived'] = 'false'
182-
query['deleted'] = 'false'
183-
query['private'] = 'false'
190+
query.archived = 'false'
191+
query.deleted = 'false'
192+
query.private = 'false'
184193
}
185194
if (datasetsStatus.value.id === 'deleted') {
186-
query['deleted'] = 'true'
195+
query.deleted = 'true'
187196
}
188197
if (datasetsStatus.value.id === 'archived') {
189-
query['archived'] = 'true'
198+
query.archived = 'true'
190199
}
191200
if (datasetsStatus.value.id === 'private') {
192-
query['private'] = 'true'
201+
query.private = 'true'
193202
}
194203
}
195204

components/Datasets/DatasetsMetrics.vue

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
</template>
8181

8282
<script setup lang="ts">
83-
import { BrandedButton, StatBox, useMetrics, createOrganizationMetricsUrl, type Organization, type OrganizationMetrics } from '@datagouv/components-next'
83+
import { BrandedButton, StatBox, useMetrics, createOrganizationMetricsUrl, type Organization } from '@datagouv/components-next'
8484
import { RiDownloadLine } from '@remixicon/vue'
8585
8686
const props = defineProps<{
@@ -93,16 +93,30 @@ const { getOrganizationMetrics } = useMetrics()
9393
const metricsOpen = ref(false)
9494
const metricsTitleId = useId()
9595
96-
const metrics = ref<OrganizationMetrics | null>(null)
97-
96+
const metricsViews = ref<null | Record<string, number>>(null)
97+
const metricsViewsTotal = ref<null | number>(null)
98+
const metricsDownloads = ref<null | Record<string, number>>(null)
99+
const metricsDownloadsTotal = ref<null | number>(null)
100+
const metricsReuses = ref<null | Record<string, number>>(null)
101+
const metricsReusesTotal = ref<null | number>(null)
102+
const metricsDataservices = ref<null | Record<string, number>>(null)
98103
watchEffect(async () => {
99104
if (!props.organization.id) return
100-
metrics.value = await getOrganizationMetrics(props.organization.id)
105+
const metrics = await getOrganizationMetrics(props.organization.id)
106+
metricsDownloads.value = metrics.downloads
107+
metricsDownloadsTotal.value = metrics.downloadsTotal
108+
metricsReuses.value = metrics.reusesViews
109+
metricsReusesTotal.value = metrics.reusesViewsTotal
110+
metricsViews.value = metrics.datasetsViews
111+
metricsViewsTotal.value = metrics.datasetsViewsTotal
112+
metricsDataservices.value = metrics.dataservicesViews
101113
})
102114
103115
const downloadStatsUrl = computed(() => {
104-
if (!metrics.value) return null
116+
if (!metricsViews.value || !metricsDownloads.value || !metricsDataservices.value || !metricsReuses.value) {
117+
return null
118+
}
105119
106-
return createOrganizationMetricsUrl(metrics.value.datasetsViews, metrics.value.downloads, metrics.value.dataservicesViews, metrics.value.reusesViews)
120+
return createOrganizationMetricsUrl(metricsViews.value, metricsDownloads.value, metricsDataservices.value, metricsReuses.value)
107121
})
108122
</script>

server/routes/nuxt-api/sitemaps/pages.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { defineSitemapEventHandler } from '#imports'
22
import type { SitemapUrl } from '#sitemap/types'
33

4+
interface GithubTreeResponse {
5+
tree: Array<{ path: string, type: string }>
6+
}
7+
48
export default defineSitemapEventHandler(async () => {
59
const config = useRuntimeConfig()
610
const repo = config.pagesGhRepoName
@@ -10,14 +14,14 @@ export default defineSitemapEventHandler(async () => {
1014
// The limit for the tree array is 100,000 entries with a maximum size of 7 MB
1115
// when using the recursive parameter.
1216

13-
return await $fetch<{ path: string, type: string }[]>(contentUrl, {
17+
return await $fetch<GithubTreeResponse>(contentUrl, {
1418
headers: { Accept: 'application/vnd.github+json' },
1519
}).then((result) => {
1620
return result.tree
1721
.filter(
18-
p => p.type === 'blob' && p.path.startsWith('pages/') && (p.path.endsWith('.html') || p.path.endsWith('.md')),
22+
(p: { path: string, type: string }) => p.type === 'blob' && p.path.startsWith('pages/') && (p.path.endsWith('.html') || p.path.endsWith('.md')),
1923
)
20-
.map(p => ({
24+
.map((p: { path: string, type: string }) => ({
2125
loc: `/${p.path.slice(0, p.path.lastIndexOf('.'))}/`,
2226
_sitemap: 'pages',
2327
} satisfies SitemapUrl))

server/routes/nuxt-api/sitemaps/urls.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,59 @@
11
import { defineSitemapEventHandler } from '#imports'
22
import type { SitemapUrl } from '#sitemap/types'
3+
import type { H3Event } from 'h3'
34

45
const API_V2_TYPES = ['dataset']
56

6-
export default defineSitemapEventHandler(async (event) => {
7+
interface TotalResponse {
8+
total: number
9+
}
10+
11+
interface PaginatedResponse {
12+
data: Array<Record<string, string>>
13+
next_page: string | null
14+
page: number
15+
total: number
16+
}
17+
18+
export default defineSitemapEventHandler(async (event: H3Event) => {
719
const config = useRuntimeConfig()
820

921
const { type, nbSitemapSections = 1 } = getQuery(event)
1022
if (!type)
1123
return new Response(null, { status: 404 })
1224
let { section = 1 } = getQuery(event)
13-
section = parseInt(section)
25+
section = parseInt(String(section))
26+
const nbSections = parseInt(String(nbSitemapSections))
1427

1528
const pageSize = 200
1629
const selfWebUrlKey = type == 'dataservice' ? 'self_web_url' : 'page'
1730

1831
// computing starting api page and max page for this sitemap section
1932
let currentPage = 1
20-
let maxPage = null
21-
await $fetch<{ path: string }[]>(`${config.public.apiBase}/api/1/${type}s/`, {
33+
let maxPage = 1
34+
await $fetch<TotalResponse>(`${config.public.apiBase}/api/1/${type}s/`, {
2235
headers: { 'X-Fields': 'total' },
2336
}).then((result) => {
2437
const total = result.total
25-
currentPage = Math.floor(((total / nbSitemapSections / pageSize)) * (section - 1)) + 1
26-
maxPage = Math.floor(((total / nbSitemapSections / pageSize)) * section)
27-
if (section == nbSitemapSections)
38+
currentPage = Math.floor(((total / nbSections / pageSize)) * (section - 1)) + 1
39+
maxPage = Math.floor(((total / nbSections / pageSize)) * section)
40+
if (section == nbSections)
2841
maxPage += 1 // add last incomplete page on the last sitemap
2942
})
3043

31-
const apiVersion = API_V2_TYPES.includes(type) ? 2 : 1
32-
let nextPageUrl = config.public.apiBase + `/api/${apiVersion}/${type}s/?page_size=${pageSize}&page=${currentPage}`
33-
const pages = []
44+
const apiVersion = API_V2_TYPES.includes(String(type)) ? 2 : 1
45+
let nextPageUrl: string | null = config.public.apiBase + `/api/${apiVersion}/${type}s/?page_size=${pageSize}&page=${currentPage}`
46+
const pages: SitemapUrl[] = []
3447

3548
do {
36-
await $fetch<{ path: string }[]>(nextPageUrl, {
49+
await $fetch<PaginatedResponse>(nextPageUrl, {
3750
headers: { 'X-Fields': `data{${selfWebUrlKey}},next_page,page,total` },
3851
}).then((result) => {
3952
nextPageUrl = result.next_page
4053
currentPage = result.page
41-
pages.push(...result.data.map(p => ({
54+
pages.push(...result.data.map((p: Record<string, string>) => ({
4255
loc: p[selfWebUrlKey],
43-
_sitemap: nbSitemapSections == 1 ? `${type}s` : `${type}s_${section}`,
56+
_sitemap: nbSections == 1 ? `${type}s` : `${type}s_${section}`,
4457
} satisfies SitemapUrl)))
4558
})
4659
} while (nextPageUrl && currentPage < maxPage)

utils/contacts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const defaultContactForm = {
66
name: '',
77
email: '',
88
contact_form: '',
9-
role: 'contact',
9+
role: 'contact' as const,
1010
}
1111

1212
export async function newContactPoint(api: $Fetch, organization: Organization, contactPoint: NewContactPoint): Promise<ContactPoint> {

0 commit comments

Comments
 (0)