Skip to content

Commit

Permalink
Add ui for application errors (#143)
Browse files Browse the repository at this point in the history
Closes #127
  • Loading branch information
chrisrohr authored Feb 24, 2023
1 parent d6cabbe commit ac02a13
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 9 deletions.
15 changes: 12 additions & 3 deletions ui/src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
round
icon="menu"
aria-label="Menu"
@click="drawer = !drawer"
@click="drawer.value = !drawer.value"
class="text-grey-4"
v-if="authStore.isLoggedIn"
/>
Expand Down Expand Up @@ -44,8 +44,8 @@
show-if-above

:mini="miniState"
@mouseover="miniState = false"
@mouseout="miniState = true"
@mouseover="miniState.value = false"
@mouseout="miniState.value = true"

bordered
:width="200"
Expand Down Expand Up @@ -108,6 +108,15 @@
</q-item-section>
</q-item>

<q-item to="/errors" exact clickable v-ripple>
<q-item-section avatar>
<q-icon name="gpp_bad"/>
</q-item-section>
<q-item-section>
Errors
</q-item-section>
</q-item>

<q-item to="/audits" exact clickable v-ripple v-if="authStore.isAdmin">
<q-item-section avatar>
<q-icon name="assignment"/>
Expand Down
91 changes: 91 additions & 0 deletions ui/src/pages/ErrorsPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<q-page padding>
<q-table title="Application Errors" :columns="errorColumns" :rows="errorStore.errors" :loading="errorStore.loading" v-model:pagination="errorStore.pagination" @request="errorStore.load">

<template v-slot:top-right>
<q-select outlined emit-value map-options v-model="errorStore.status" :options="filterOptions" label="Filter By" dense style="min-width: 150px" @update:model-value="errorStore.load()"/>
<q-btn icon="fa-solid fa-wand-sparkles" label="Resolve All" @click="errorStore.resolveAll()" class="on-right"/>
</template>

<template v-slot:body-cell-firstOccurred="props">
<q-td :props="props">
<span>
{{ fromNow(props.row.createdAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.createdAt) }}
</q-tooltip>
</span>
</q-td>
</template>

<template v-slot:body-cell-lastOccurred="props">
<q-td :props="props">
<span>
{{ fromNow(props.row.updatedAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.updatedAt) }}
</q-tooltip>
</span>
</q-td>
</template>

<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn size="sm" icon="fa-solid fa-wand-sparkles" @click="errorStore.resolve(props.row.id)">
<q-tooltip>Resolve error</q-tooltip>
</q-btn>
</q-td>
</template>

</q-table>
</q-page>
</template>

<script setup>
import { onMounted } from 'vue'
import { formatDate, fromNow } from '../utils/time'
import { useErrorStore } from 'stores/errorStore'
// Stores
const errorStore = useErrorStore()
// Reactive data
// Constant data
const filterOptions = ['UNRESOLVED', 'RESOLVED']
const errorColumns = [
{
name: 'description',
label: 'Description',
field: 'description',
align: 'left'
},
{
name: 'numberOfOccurrences',
label: 'Number of Occurrences',
field: 'numTimesOccurred',
align: 'left'
},
{
name: 'firstOccurred',
label: 'First Occurred',
align: 'left'
},
{
name: 'lastOccurred',
label: 'Last Occurred',
align: 'left'
},
{
name: 'actions',
label: 'Actions',
align: 'left'
}
]
// Methods
onMounted(() => {
errorStore.load()
})
</script>
3 changes: 2 additions & 1 deletion ui/src/router/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const routes = [
{ path: '/audits', name: 'AuditRecordsPage', component: () => import('pages/AuditRecordsPage.vue') },
{ path: '/tasks', name: 'ManualTaskPage', component: () => import('pages/ManualTaskPage.vue') },
{ path: '/builds', name: 'BuildsPage', component: () => import('pages/BuildsPage.vue') },
{ path: '/hostConfig', name: 'HostConfigurationPage', component: () => import('pages/HostConfigurationPage.vue') }
{ path: '/hostConfig', name: 'HostConfigurationPage', component: () => import('pages/HostConfigurationPage.vue') },
{ path: '/errors', name: 'ErrorsPage', component: () => import('pages/ErrorsPage.vue') }
]
},

Expand Down
46 changes: 46 additions & 0 deletions ui/src/stores/errorStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineStore } from 'pinia'
import { api } from 'src/boot/axios'
import { ref } from 'vue'

export const useErrorStore = defineStore('error', () => {
const errors = ref([])
const loading = ref(false)
const pagination = ref({
page: 1,
rowsPerPage: 25,
rowsNumber: 0
})
const status = ref('UNRESOLVED')

async function load (props) {
loading.value = true

if (props !== undefined) {
pagination.value.page = props.pagination.page
pagination.value.rowsPerPage = props.pagination.rowsPerPage
}

const params = {
pageNumber: pagination.value.page,
pageSize: pagination.value.rowsPerPage,
status: status.value
}

const response = await api.get('/kiwi/application-errors', { params })
errors.value = response.data.items
pagination.value.rowsNumber = response.data.totalCount
loading.value = false
}

function resolve (id) {
return api.put(`/kiwi/application-errors/resolve/${id}`)
.then(() => load())
}

function resolveAll () {
return api.put('/kiwi/application-errors/resolve')
.then(() => load())
}

return { errors, loading, pagination, load, resolve, resolveAll, status }
})
19 changes: 19 additions & 0 deletions ui/test/vitest/__tests__/pages/ErrorsPage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import ErrorsPage from 'pages/ErrorsPage.vue'
import { createTestingPinia } from '@pinia/testing'

installQuasar()

describe('ErrorsPage', () => {
it('should mount the errors page without errors', () => {
const wrapper = shallowMount(ErrorsPage, {
global: {
plugins: [createTestingPinia()]
}
})

expect(wrapper).toBeTruthy()
})
})
61 changes: 61 additions & 0 deletions ui/test/vitest/__tests__/stores/errorStore.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { setActivePinia, createPinia } from 'pinia'
import { useErrorStore } from 'src/stores/errorStore'
import { beforeEach, vi, describe, it, expect } from 'vitest'
import { api } from 'src/boot/axios'

beforeEach(() => {
setActivePinia(createPinia())

vi.mock('src/boot/axios', () => {
const api = {
get: vi.fn().mockImplementation(() => Promise.resolve({ data: { name: 'dev' } })),
put: vi.fn().mockImplementation(() => Promise.resolve(true))
}

return { api }
})
})

describe('load', () => {
beforeEach(() => {
setActivePinia(createPinia())
})

it('should use defaults', () => {
const errorStore = useErrorStore()

errorStore.load()

expect(api.get).toHaveBeenCalled()
expect(api.get).toHaveBeenCalledWith('/kiwi/application-errors', { params: { pageNumber: 1, pageSize: 25, status: 'UNRESOLVED' } })
})

it('should use provided props', () => {
const errorStore = useErrorStore()

errorStore.load({ pagination: { page: 5, rowsPerPage: 50 } })

expect(api.get).toHaveBeenCalled()
expect(api.get).toHaveBeenCalledWith('/kiwi/application-errors', { params: { pageNumber: 5, pageSize: 50, status: 'UNRESOLVED' } })
})
})

describe('resolve', () => {
it('should call resolve for given id', () => {
const errorStore = useErrorStore()

errorStore.resolve(1)

expect(api.put).toHaveBeenCalledWith('/kiwi/application-errors/resolve/1')
})
})

describe('resolveAll', () => {
it('should call resolveAll', () => {
const errorStore = useErrorStore()

errorStore.resolveAll()

expect(api.put).toHaveBeenCalledWith('/kiwi/application-errors/resolve')
})
})
8 changes: 3 additions & 5 deletions ui/test/vitest/__tests__/stores/hostStore.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { setActivePinia, createPinia } from 'pinia'
import { useHostStore } from 'src/stores/hostStore'
import { vi } from "vitest";
import { api } from "src/boot/axios";
import { vi, beforeEach, describe, it, expect } from 'vitest'
import { api } from 'src/boot/axios'

beforeEach(() => {
setActivePinia(createPinia())

vi.mock('src/boot/axios', () => {
const api = {
get: vi.fn().mockImplementation(() => Promise.resolve({data: { name: 'dev' }})),
get: vi.fn().mockImplementation(() => Promise.resolve({ data: { name: 'dev' } })),
post: vi.fn().mockImplementation(() => Promise.resolve(true)),
delete: vi.fn().mockImplementation(() => Promise.resolve(true)),
put: vi.fn().mockImplementation(() => Promise.resolve(true))
Expand All @@ -19,7 +19,6 @@ beforeEach(() => {
})

describe('load', () => {

it('should load hosts', async () => {
const hostStore = useHostStore()
hostStore.environmentFilter = { value: 1 }
Expand All @@ -29,7 +28,6 @@ describe('load', () => {
expect(api.get).toHaveBeenCalled()
expect(api.get).toHaveBeenCalledWith('/host/1', { componentFilter: '' })
})

})

describe('create', () => {
Expand Down

0 comments on commit ac02a13

Please sign in to comment.