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

Rework report viewer error handling #1362

Merged
merged 13 commits into from
Nov 28, 2023
6 changes: 6 additions & 0 deletions report-viewer/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ app.use(createPinia())
app.use(router)
app.use(VueVirtualScroller)

app.config.errorHandler = (err, vm, info) => {
console.error(err)
console.error(info)
alert('An unhandeled error occured. Please check the console for more details.')
}

app.mount('#app')
37 changes: 34 additions & 3 deletions report-viewer/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ const router = createRouter({
props: true
},
{
path: '/error',
path: '/error/:message/:to?/:routerInfo?',
name: 'ErrorView',
component: ErrorView
component: ErrorView,
props: true
},
{
path: '/cluster/:clusterIndex',
Expand All @@ -43,8 +44,38 @@ const router = createRouter({
path: '/info',
name: 'InfoView',
component: InformationViewWrapper
},
{
path: '/:pathMatch(.*)*',
redirect: '/error/Could not find the requested page/FileUploadView/Back to file upload'
}
]
})

export { router }
function redirectOnError(
error: Error,
prefix: string = '',
redirectRoute: string = 'FileUploadView',
redirectRouteTitle: string = 'Back to file upload'
) {
console.error(error)
router.push({
name: 'ErrorView',
params: {
message: prefix + error.message,
to: redirectRoute,
routerInfo: redirectRouteTitle
}
})
}

let hasHadRouterError = false
router.onError((error) => {
if (hasHadRouterError) {
return alert('An error occurred while routing. Please reload the page.')
}
hasHadRouterError = true
redirectOnError(error, 'An error occurred while routing. Please reload the page.\n')
})

export { router, redirectOnError }
11 changes: 8 additions & 3 deletions report-viewer/src/viewWrapper/ClusterViewWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { OverviewFactory } from '@/model/factories/OverviewFactory'
import ClusterView from '@/views/ClusterView.vue'
import LoadingCircle from '@/components/LoadingCircle.vue'
import type { Overview } from '@/model/Overview'
import { redirectOnError } from '@/router'

const props = defineProps({
clusterIndex: {
Expand All @@ -26,7 +27,11 @@ const clusterIndex = computed(() => parseInt(props.clusterIndex))

const overview: Ref<Overview | null> = ref(null)

OverviewFactory.getOverview().then((o) => {
overview.value = o
})
OverviewFactory.getOverview()
.then((o) => {
overview.value = o
})
.catch((error) => {
redirectOnError(error, 'Could not load cluster:\n', 'OverviewView', 'Back to overview')
})
</script>
21 changes: 15 additions & 6 deletions report-viewer/src/viewWrapper/ComparisonViewWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { getHighlightLanguage, type HighlightLanguage } from '@/model/Language'
import type { Comparison } from '@/model/Comparison'
import { ComparisonFactory } from '@/model/factories/ComparisonFactory'
import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'

const props = defineProps({
firstId: {
Expand All @@ -39,11 +40,19 @@ const language: Ref<HighlightLanguage | null> = ref(null)

// This eslint rule is disabled to allow the use of await in the setup function. Disabling this rule is safe, because the props are gathered from the url, so changing them would reload the pafe anyway.
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
ComparisonFactory.getComparison(props.firstId, props.secondId).then((comp) => {
comparison.value = comp
})
ComparisonFactory.getComparison(props.firstId, props.secondId)
.then((comp) => {
comparison.value = comp
})
.catch((error) => {
redirectOnError(error, 'Could not load comparison:\n', 'OverviewView', 'Back to overview')
})

OverviewFactory.getOverview().then((overview) => {
language.value = getHighlightLanguage(overview.language)
})
OverviewFactory.getOverview()
.then((overview) => {
language.value = getHighlightLanguage(overview.language)
})
.catch((error) => {
redirectOnError(error, 'Could not load coparison:\n')
})
</script>
11 changes: 8 additions & 3 deletions report-viewer/src/viewWrapper/InformationViewWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ import { OverviewFactory } from '@/model/factories/OverviewFactory'
import InformationView from '@/views/InformationView.vue'
import type { Overview } from '@/model/Overview'
import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'

const overview: Ref<Overview | null> = ref(null)

OverviewFactory.getOverview().then((o) => {
overview.value = o
})
OverviewFactory.getOverview()
.then((o) => {
overview.value = o
})
.catch((error) => {
redirectOnError(error, 'Could not load information:\n', 'OverviewView', 'Back to overview')
})
</script>
11 changes: 8 additions & 3 deletions report-viewer/src/viewWrapper/OverviewViewWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ import { OverviewFactory } from '@/model/factories/OverviewFactory'
import OverviewView from '@/views/OverviewView.vue'
import type { Overview } from '@/model/Overview'
import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'

const overview: Ref<Overview | null> = ref(null)

OverviewFactory.getOverview().then((o) => {
overview.value = o
})
OverviewFactory.getOverview()
.then((o) => {
overview.value = o
})
.catch((error) => {
redirectOnError(error, 'Could not load overview:\n')
})
</script>
8 changes: 7 additions & 1 deletion report-viewer/src/views/ClusterView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import type { ClusterListElement, ClusterListElementMember } from '@/model/Clust
import type { ComparisonListElement } from '@/model/ComparisonListElement'
import { MetricType } from '@/model/MetricType'
import type { Overview } from '@/model/Overview'
import { computed, type PropType, type Ref } from 'vue'
import { redirectOnError } from '@/router'
import { computed, onErrorCaptured, type PropType, type Ref } from 'vue'

const props = defineProps({
overview: {
Expand Down Expand Up @@ -94,4 +95,9 @@ const clusterListElement: Ref<ClusterListElement> = computed(() => {
strength: props.cluster.strength
}
})

onErrorCaptured((error) => {
redirectOnError(error, 'Error displaying cluster:\n', 'OverviewView', 'Back to overview')
return false
})
</script>
17 changes: 4 additions & 13 deletions report-viewer/src/views/ComparisonView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<script setup lang="ts">
import type { Match } from '@/model/Match'

import { onMounted, ref, watch, type Ref, computed, onErrorCaptured, type PropType } from 'vue'
import { onMounted, ref, watch, type Ref, computed, type PropType, onErrorCaptured } from 'vue'
import TextInformation from '@/components/TextInformation.vue'
import MatchList from '@/components/MatchList.vue'
import FilesContainer from '@/components/FilesContainer.vue'
Expand All @@ -76,9 +76,9 @@ import Container from '@/components/ContainerComponent.vue'
import { HighlightLanguage } from '@/model/Language'
import hljsLightMode from 'highlight.js/styles/vs.css?raw'
import hljsDarkMode from 'highlight.js/styles/vs2015.css?raw'
import { router } from '@/router'
import { MetricType } from '@/model/MetricType'
import { Comparison } from '@/model/Comparison'
import { redirectOnError } from '@/router'

const props = defineProps({
firstId: {
Expand Down Expand Up @@ -166,17 +166,8 @@ watch(useDarkMode, (newValue) => {
styleHolderDiv.appendChild(styleElement)
})

onErrorCaptured((e) => {
console.log(e)
router.push({
name: 'ErrorView',
state: {
message: 'Overview.json could not be found!',
to: '/',
routerInfo: 'back to FileUpload page'
}
})
store().clearStore()
onErrorCaptured((error) => {
redirectOnError(error, 'Error displaying comparison:\n', 'OverviewView', 'Back to overview')
return false
})
</script>
46 changes: 37 additions & 9 deletions report-viewer/src/views/ErrorView.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<template>
<div class="flex h-screen items-center text-center">
<div class="flex h-screen text-center">
<div class="w-screen">
<div>
<img
class="mx-auto mt-8 h-auto w-60"
class="mx-auto mt-32 h-auto w-60"
src="@/assets/jplag-light-transparent.png"
alt="JPlag Logo"
v-if="store().uiState.useDarkMode"
/>
<img
class="mx-auto mt-8 h-auto w-60"
class="mx-auto mt-32 h-auto w-60"
src="@/assets/jplag-dark-transparent.png"
alt="JPlag Logo"
v-else
Expand All @@ -20,9 +20,15 @@
<h3 class="text-2xl font-bold">There was an Error!</h3>
<p class="text-xl">{{ message }}</p>
</div>
<Interactable class="mx-auto w-fit !border-accent-dark !bg-accent !bg-opacity-50">
<RouterLink :to="to">{{ routerInfo }}</RouterLink>
</Interactable>
<RouterLink
:to="{
name: to
}"
>
<Interactable class="mx-auto mt-2 w-fit !border-accent-dark !bg-accent !bg-opacity-50">
{{ routerInfo }}
</Interactable>
</RouterLink>
</Container>
</div>
</div>
Expand All @@ -32,8 +38,30 @@
import Container from '@/components/ContainerComponent.vue'
import Interactable from '@/components/InteractableComponent.vue'
import { store } from '@/stores/store'
import { onErrorCaptured } from 'vue'

const message = history.state.message as string
const to = history.state.to as string
const routerInfo = history.state.routerInfo as string
defineProps({
message: {
type: String,
required: true
},
to: {
type: String,
required: false,
default: 'FileUploadView'
},
routerInfo: {
type: String,
required: false,
default: 'Back to file upload page'
}
})

onErrorCaptured((error) => {
console.error(error)
alert(
'An error occured that could not be handeled. Please check the console for more information.'
)
return false
})
</script>
49 changes: 38 additions & 11 deletions report-viewer/src/views/FileUploadView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@
</Button>
</div>
<LoadingCircle v-else class="space-y-5 pt-5" />
<div v-if="errors.length > 0" class="text-error">
<p>{{ getErrorText() }}</p>
<p>For more details check the console.</p>
</div>
</div>
<VersionInfoComponent class="absolute bottom-3 left-3" />
</div>
</template>

<script setup lang="ts">
import { ref, type Ref } from 'vue'
import { onErrorCaptured, ref, type Ref } from 'vue'
import { useRoute } from 'vue-router'
import { router } from '@/router'
import { store } from '@/stores/store'
Expand Down Expand Up @@ -72,6 +76,8 @@ fetch('/results.zip')
.catch(() => {})

const loadingFiles = ref(false)
type fileMethod = 'query' | 'local' | 'upload' | 'unknown'
const errors: Ref<{ error: Error; source: fileMethod }[]> = ref([])

// Loads file passed in query param, if any.
const queryParams = useRoute().query
Expand All @@ -80,12 +86,8 @@ if (typeof queryParams.file === 'string' && queryParams.file !== '') {
try {
queryFileURL = new URL(queryParams.file)
} catch (e) {
if (e instanceof TypeError) {
console.warn(`Invalid URL '${queryParams.file}'`)
queryFileURL = null
} else {
throw e
}
registerError(e as Error, 'query')
queryFileURL = null
}
}
if (queryFileURL !== null) {
Expand Down Expand Up @@ -165,8 +167,7 @@ async function uploadFileOnDrag(e: DragEvent) {
throw new Error('Not exactly one file')
}
} catch (e) {
alert((e as Error).message)
throw e
registerError(e as Error, 'upload')
}
}

Expand Down Expand Up @@ -200,8 +201,7 @@ async function loadQueryFile(url: URL) {
}
await handleFile(await response.blob())
} catch (e) {
console.warn(e)
alert(e)
registerError(e as Error, 'query')
}
}

Expand All @@ -216,6 +216,33 @@ function continueWithLocal() {
})
navigateToOverview()
}

function registerError(error: Error, source: fileMethod) {
loadingFiles.value = false
errors.value.push({ error, source })
console.error(error)
}

function getErrorText() {
function getSourceText(source: fileMethod) {
if (source == 'unknown') {
return 'Error:'
}
const longNames = {
query: 'querying files',
local: 'getting local files',
upload: 'loading files'
}
return 'Error during ' + longNames[source]
}

return errors.value.map((e) => `${getSourceText(e.source)}: ${e.error.message}`).join('\n')
}

onErrorCaptured((error) => {
registerError(error, 'unknown')
return false
})
</script>

<style scoped>
Expand Down
Loading