From ac02a13f5a4a270616edef733131cc6e741a1fe5 Mon Sep 17 00:00:00 2001
From: Chris Rohr <51920+chrisrohr@users.noreply.github.com>
Date: Fri, 24 Feb 2023 15:50:54 -0500
Subject: [PATCH] Add ui for application errors (#143)
Closes #127
---
ui/src/layouts/MainLayout.vue | 15 ++-
ui/src/pages/ErrorsPage.vue | 91 +++++++++++++++++++
ui/src/router/routes.js | 3 +-
ui/src/stores/errorStore.js | 46 ++++++++++
.../vitest/__tests__/pages/ErrorsPage.spec.js | 19 ++++
.../__tests__/stores/errorStore.spec.js | 61 +++++++++++++
.../vitest/__tests__/stores/hostStore.spec.js | 8 +-
7 files changed, 234 insertions(+), 9 deletions(-)
create mode 100644 ui/src/pages/ErrorsPage.vue
create mode 100644 ui/src/stores/errorStore.js
create mode 100644 ui/test/vitest/__tests__/pages/ErrorsPage.spec.js
create mode 100644 ui/test/vitest/__tests__/stores/errorStore.spec.js
diff --git a/ui/src/layouts/MainLayout.vue b/ui/src/layouts/MainLayout.vue
index 8e2742b8..b029e829 100644
--- a/ui/src/layouts/MainLayout.vue
+++ b/ui/src/layouts/MainLayout.vue
@@ -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"
/>
@@ -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"
@@ -108,6 +108,15 @@
+
+
+
+
+
+ Errors
+
+
+
diff --git a/ui/src/pages/ErrorsPage.vue b/ui/src/pages/ErrorsPage.vue
new file mode 100644
index 00000000..afb7a5a7
--- /dev/null
+++ b/ui/src/pages/ErrorsPage.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ fromNow(props.row.createdAt) }}
+
+ {{ formatDate(props.row.createdAt) }}
+
+
+
+
+
+
+
+
+ {{ fromNow(props.row.updatedAt) }}
+
+ {{ formatDate(props.row.updatedAt) }}
+
+
+
+
+
+
+
+
+ Resolve error
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js
index 0ad915ec..449d8344 100644
--- a/ui/src/router/routes.js
+++ b/ui/src/router/routes.js
@@ -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') }
]
},
diff --git a/ui/src/stores/errorStore.js b/ui/src/stores/errorStore.js
new file mode 100644
index 00000000..a96d3281
--- /dev/null
+++ b/ui/src/stores/errorStore.js
@@ -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 }
+})
diff --git a/ui/test/vitest/__tests__/pages/ErrorsPage.spec.js b/ui/test/vitest/__tests__/pages/ErrorsPage.spec.js
new file mode 100644
index 00000000..1f460781
--- /dev/null
+++ b/ui/test/vitest/__tests__/pages/ErrorsPage.spec.js
@@ -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()
+ })
+})
diff --git a/ui/test/vitest/__tests__/stores/errorStore.spec.js b/ui/test/vitest/__tests__/stores/errorStore.spec.js
new file mode 100644
index 00000000..55aa2681
--- /dev/null
+++ b/ui/test/vitest/__tests__/stores/errorStore.spec.js
@@ -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')
+ })
+})
diff --git a/ui/test/vitest/__tests__/stores/hostStore.spec.js b/ui/test/vitest/__tests__/stores/hostStore.spec.js
index d3ad736d..2cc27818 100644
--- a/ui/test/vitest/__tests__/stores/hostStore.spec.js
+++ b/ui/test/vitest/__tests__/stores/hostStore.spec.js
@@ -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))
@@ -19,7 +19,6 @@ beforeEach(() => {
})
describe('load', () => {
-
it('should load hosts', async () => {
const hostStore = useHostStore()
hostStore.environmentFilter = { value: 1 }
@@ -29,7 +28,6 @@ describe('load', () => {
expect(api.get).toHaveBeenCalled()
expect(api.get).toHaveBeenCalledWith('/host/1', { componentFilter: '' })
})
-
})
describe('create', () => {