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

Changes logs data is not refresh during client side navigation #277

Merged
Show file tree
Hide file tree
Changes from all 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
34 changes: 27 additions & 7 deletions components/LogFilters.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
<script setup lang="ts">
import type { LocationQuery } from 'vue-router'
import type { Log } from '~/libs/types'
import { countBy, indexBy, sortBy, uniq } from 'underscore'

//
// Props
//
const props = defineProps<{
logs: Log[]
}>()

//
// Composables
//
const route = useRoute()

//
// Data
//
const filters = ref<LocationQuery>()

//
// Watchers
//
watchEffect(() => {
filters.value = route.query
})

const logs = useLogs()

//
// Computed
//
const stats = computed(() => {
const actions = logs.value
const actions = props.logs
.map((log) =>
uniq(
[
Expand All @@ -27,19 +47,19 @@ const stats = computed(() => {
})

const statSelectors = computed(() => {
const matches = logs.value.map((log) => uniq(log.matches).flat()).flat(1)
const matches = props.logs.map((log) => uniq(log.matches).flat()).flat(1)
return getStats(matches, (m) => m.selectors.join(';'))
})

const statUserGroups = computed(() => {
const userGroups = logs.value
const userGroups = props.logs
.map((log) => uniq(log.matches.map((m) => m.user_groups).flat(2)))
.flat(1)
return getStats(userGroups)
})

const statUsers = computed(() => {
const users = logs.value
const users = props.logs
.map((log) =>
uniq(
(log.changesets ? log.base ? log.changesets.slice(1) : log.changesets : []).map(
Expand All @@ -52,7 +72,7 @@ const statUsers = computed(() => {
})

const statDates = computed(() => {
const dates = logs.value.map((log) => log.change.created.substring(0, 10))
const dates = props.logs.map((log) => log.change.created.substring(0, 10))
return getStats(dates).sort()
})

Expand Down
65 changes: 65 additions & 0 deletions composables/useChangesLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { LoCha, Log, Project } from '~/libs/types'

interface ChangesLogsData {
project: Project
loChas: LoCha[]
logs: Log[]
fetchedAt: Date
}
export function useChangesLogs(projectSlug: string) {
const nuxtApp = useNuxtApp()
const config = useRuntimeConfig()

return useAsyncData<ChangesLogsData>(
`changes_logs-${projectSlug}`,
async () => {
try {
// Fetch project and change logs data concurrently
const [project, loChas] = await Promise.all([
$fetch<Project>(`${config.public.api}/projects/${projectSlug}`),
$fetch<LoCha[]>(`${config.public.api}/projects/${projectSlug}/changes_logs`),
])

return {
project,
loChas,
logs: loChas.map((loCha) => loCha.objects).flat(),
fetchedAt: new Date(),
}
}
catch (err) {
if (err instanceof Error) {
ElMessage.error({
duration: 0,
message: err.message,
})

throw new Error(err.message)
}
else {
throw new TypeError('Failed to fetch data')
}
}
},
{
getCachedData(key) {
const data = nuxtApp.payload.data[key] || nuxtApp.static.data[key]

if (!data) {
return null
}

// Check if the cached data has expired
const expirationDate = new Date(data.fetchedAt)
expirationDate.setTime(expirationDate.getTime() + 5 * 1000)
const isExpired = expirationDate.getTime() < Date.now()

if (isExpired) {
return null
}

return data
},
},
)
}
156 changes: 74 additions & 82 deletions pages/[project]/changes_logs.vue
Original file line number Diff line number Diff line change
@@ -1,76 +1,57 @@
<script setup lang="ts">
import type { Geometry } from 'geojson'
import type { LoCha, Project } from '~/libs/types'
import { uniq } from 'underscore'

//
// Validators
//
definePageMeta({
validate({ params }) {
return /^[-\w:]+$/.test(params.project as string)
},
})

//
// Composables
//
const router = useRouter()
const route = useRoute()
const projectSlug = route.params.project as string
const project = ref<Project>()
const loChas = useLoChas()
const logs = useLogs()
const projectSlug = route.params.project.toString()
const config = useRuntimeConfig()
const user = useUser()
const { data, status, refresh } = useChangesLogs(projectSlug)

try {
const projectData = await useFetchWithCache<Project>(`project-${projectSlug}`, `${config.public.api}/projects/${projectSlug}`)
project.value = projectData.value
}
catch (err: any) {
ElMessage.error({
duration: 0,
message: err.message,
})
}

try {
const loChasData = await useFetchWithCache<LoCha[]>(`loChas-${projectSlug}`, `${config.public.api}/projects/${projectSlug}/changes_logs`)
loChas.value = loChasData.value

if (loChasData.value.length) {
logs.value = loChasData.value.map((loCha) => loCha.objects).flat()
}
}
catch (err: any) {
ElMessage.error({
duration: 0,
message: err.message,
})
}

//
// Computed
//
const baseGeoms = computed(() => {
if (!logs.value) {
if (!data.value?.logs) {
return []
}

return logs.value
return data.value.logs
.map((log) => log.base?.geom)
.filter((geom): geom is Geometry => !!geom)
})

const changeGeoms = computed(() => {
if (!logs.value) {
if (!data.value?.logs) {
return []
}

return logs.value.map((log) => log.change.geom)
return data.value.logs.map((log) => log.change.geom)
})

const user = useUser()
const isProjectUser = computed(() => {
return !!user.value?.projects?.includes(projectSlug)
})

const loChasWithFilter = computed(() => {
if (!loChas.value.length) {
if (!data.value?.loChas.length) {
return []
}

return loChas.value.filter((loCha) =>
return data.value?.loChas.filter((loCha) =>
loCha.objects.some((log) => {
const changesetsUsers
= route.query.filterByUsers !== undefined
Expand Down Expand Up @@ -106,38 +87,44 @@ const loChasWithFilter = computed(() => {
)
})

function removeLogs(loChaIds?: number[]) {
loChas.value = loChas.value
.filter((loCha) => !loChaIds || loChaIds.findIndex(
(loChaId) => loCha.id === loChaId,
) === -1)

logs.value = loChas.value.map((loCha) => loCha.objects).flat()
project.value!.to_be_validated = logs.value?.length
}
//
// Methods
//

async function handleAccept(loChaIds?: number[]) {
try {
if (!loChaIds) {
loChaIds = loChasWithFilter.value.map((loCha) => loCha.id)
}

await $fetch(
`${config.public.api}/projects/${projectSlug}/changes_logs/accept`,
{
try {
await $fetch(`${config.public.api}/projects/${projectSlug}/changes_logs/accept`, {
credentials: 'include',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(loChaIds),
},
)
removeLogs(loChaIds)
})
await refresh()

if (!loChasWithFilter.value.length) {
resetFilters()
if (!loChasWithFilter.value.length) {
resetFilters()
}
}
catch (err) {
if (err instanceof Error) {
ElMessage.error({
duration: 0,
message: err.message,
})

throw new Error(err.message)
}
else {
throw new TypeError('Failed to fetch data')
}
}
}
catch (err: any) {
Expand All @@ -148,7 +135,6 @@ async function handleAccept(loChaIds?: number[]) {
}
}

const router = useRouter()
async function resetFilters() {
await router.replace({ ...route, query: undefined })
}
Expand All @@ -159,33 +145,39 @@ function matchFilterBySelectors(selectors: string[]) {
</script>

<template>
<el-main>
<project-light v-if="project" :project="project" title-tag="h1" />
<el-divider border-style="dotted" />
<el-row>
<diff-map
:base-geom="baseGeoms"
:change-geom="changeGeoms"
style="resize: vertical"
<el-main
v-loading.lock="status === 'pending'"
element-loading-background="#FAFAFA"
element-loading-text="Loading..."
>
<el-alert v-if="status === 'idle' && !data" title="No data available at the moment." type="warning" />
<el-container v-if="data && status === 'success'" direction="vertical">
<project-light :project="data.project" title-tag="h1" />
<el-row>
<diff-map
:base-geom="baseGeoms"
:change-geom="changeGeoms"
style="resize: vertical"
/>
</el-row>
<log-filters :logs="data.logs" />
<log-validator-bulk
v-if="isProjectUser && Object.keys(route.query).length"
@bulk-validation="handleAccept"
/>
<h3>{{ $t('logs.data') }}</h3>
<p>{{ $t('logs.data_details') }}</p>
<ul>
<li>{{ $t('logs.data_details_osm') }}</li>
<li>{{ $t('logs.data_details_manual') }}</li>
</ul>
<lo-cha-list
v-if="data.loChas.length"
:project-slug="projectSlug"
:lo-chas="loChasWithFilter"
@accept="handleAccept([$event])"
/>
</el-row>
<log-filters v-if="logs" />
<log-validator-bulk
v-if="isProjectUser && Object.keys(route.query).length"
@bulk-validation="handleAccept"
/>
<h3>{{ $t('logs.data') }}</h3>
<p>{{ $t('logs.data_details') }}</p>
<ul>
<li>{{ $t('logs.data_details_osm') }}</li>
<li>{{ $t('logs.data_details_manual') }}</li>
</ul>
<lo-cha-list
v-if="loChas?.length"
:project-slug="projectSlug"
:lo-chas="loChasWithFilter"
@accept="handleAccept([$event])"
/>
</el-container>
</el-main>
</template>

Expand Down
Loading