Skip to content

Commit

Permalink
Add Admin view systems page (#150)
Browse files Browse the repository at this point in the history
Add admin view systems page

Relates to #140
  • Loading branch information
chrisrohr authored Mar 7, 2023
1 parent cfb1373 commit 4943734
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 3 deletions.
2 changes: 2 additions & 0 deletions service/src/main/java/org/kiwiproject/champagne/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.kiwiproject.champagne.resource.AuditRecordResource;
import org.kiwiproject.champagne.resource.AuthResource;
import org.kiwiproject.champagne.resource.BuildResource;
import org.kiwiproject.champagne.resource.DeployableSystemResource;
import org.kiwiproject.champagne.resource.DeploymentEnvironmentResource;
import org.kiwiproject.champagne.resource.HostConfigurationResource;
import org.kiwiproject.champagne.resource.TaskResource;
Expand Down Expand Up @@ -116,6 +117,7 @@ public void run(AppConfig configuration, Environment environment) {
environment.jersey().register(new TaskResource(releaseDao, releaseStatusDao, taskDao, taskStatusDao, deploymentEnvironmentDao, auditRecordDao, errorDao));
environment.jersey().register(new UserResource(userDao, auditRecordDao, errorDao));
environment.jersey().register(new ApplicationErrorResource(errorDao));
environment.jersey().register(new DeployableSystemResource(deployableSystemDao, userDao, auditRecordDao, errorDao));

configureCors(environment);

Expand Down
15 changes: 13 additions & 2 deletions ui/src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
show-if-above

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

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

<q-separator />

<q-item to="/audits" exact clickable v-ripple v-if="authStore.isAdmin">
<q-item-section avatar>
<q-icon name="assignment"/>
Expand All @@ -125,6 +127,15 @@
Audit
</q-item-section>
</q-item>

<q-item to="/adminSystems" exact clickable v-ripple v-if="authStore.isAdmin">
<q-item-section avatar>
<q-icon name="conveyor_belt"/>
</q-item-section>
<q-item-section>
Systems
</q-item-section>
</q-item>
</q-list>
</q-scroll-area>
</q-drawer>
Expand Down
116 changes: 116 additions & 0 deletions ui/src/pages/AdminSystemPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<template>
<q-page padding>
<q-table title="Deployable Systems" :columns="systemColumns" :rows="adminSystemStore.systems" :loading="adminSystemStore.loading" v-model:pagination="adminSystemStore.pagination" @request="adminSystemStore.load">

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

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

<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn size="sm" icon="delete" @click="confirmDelete(props.row)">
<q-tooltip>Delete System</q-tooltip>
</q-btn>
</q-td>
</template>

</q-table>

<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-btn fab icon="add" color="accent" @click="showSystemAdd = true" />
</q-page-sticky>

<q-dialog v-model="showSystemAdd">
<q-card>
<q-card-section>
<div class="text-h5">Add new deployable system</div>
</q-card-section>
<q-card-section>
<div class="row q-mb-md">
<q-input dense style="min-width: 500px" v-model="activeSystem.name" label="System Name" :rules="[val => !!val || 'System Name is required']"/>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat v-close-popup>Cancel</q-btn>
<q-btn flat color="primary" @click="createSystem">Save</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
</q-page>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import { formatDate, fromNow } from '../utils/time'
import { confirmAction } from '../utils/alerts'
import { useAdminSystemStore } from 'stores/adminSystemStore'
const adminSystemStore = useAdminSystemStore()
// Reactive data
const showSystemAdd = ref(false)
const activeSystem = ref({
name: ''
})
// Constant data
const systemColumns = [
{
name: 'name',
label: 'Name',
field: 'name',
align: 'left'
},
{
name: 'createdAt',
label: 'Created At',
align: 'left'
},
{
name: 'updatedAt',
label: 'Updated At',
align: 'left'
},
{
name: 'actions',
label: 'Actions',
align: 'left'
}
]
// Methods
function createSystem () {
showSystemAdd.value = false
adminSystemStore.create(activeSystem.value)
}
function confirmDelete (system) {
confirmAction(
`Are you sure you want to delete system ${system.name}? This will cause all related components to be deleted as well!`,
() => deleteSystem(system.id)
)
}
function deleteSystem (id) {
adminSystemStore.deleteSystem(id)
}
onMounted(() => {
adminSystemStore.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 @@ -12,7 +12,8 @@ const routes = [
{ 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: '/errors', name: 'ErrorsPage', component: () => import('pages/ErrorsPage.vue') }
{ path: '/errors', name: 'ErrorsPage', component: () => import('pages/ErrorsPage.vue') },
{ path: '/adminSystems', name: 'AdminSystemPage', component: () => import('pages/AdminSystemPage.vue') }
]
},

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

export const useAdminSystemStore = defineStore('adminSystem', () => {
const systems = ref([])
const loading = ref(false)
const pagination = ref({
page: 1,
rowsPerPage: 25,
rowsNumber: 0
})

async function load (props) {
doPagedRequest(loading, props, pagination, '/systems/admin', systems)
}

function create (systemData) {
return api.post('/systems', systemData)
.then(() => load())
}

function deleteSystem (id) {
api.delete(`/systems/${id}`)
.then(() => load())
}

return { systems, loading, load, create, deleteSystem }
})
19 changes: 19 additions & 0 deletions ui/src/stores/systemStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineStore } from 'pinia'
import { api } from 'src/boot/axios'
import { ref } from 'vue'

export const useSystemStore = defineStore('system', () => {
const systems = ref([])
const loading = ref(false)
const currentSystem = ref(null)

async function load () {
loading.value = true

const response = await api.get('/systems')
systems.value = response.data
loading.value = false
}

return { systems, loading, currentSystem, load }
})
61 changes: 61 additions & 0 deletions ui/test/vitest/__tests__/pages/AdminSystemPage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import AdminSystemPage from 'pages/AdminSystemPage.vue'
import { createTestingPinia } from '@pinia/testing'
import { useAdminSystemStore } from 'src/stores/adminSystemStore'
import { confirmAction } from 'src/utils/alerts'

installQuasar()

vi.mock('src/utils/alerts', () => {
const confirmAction = vi.fn()

return { confirmAction }
})

const wrapper = shallowMount(AdminSystemPage, {
global: {
plugins: [createTestingPinia()]
}
})

describe('AdminSystemPage', () => {
it('should mount the admin system page without errors', () => {
expect(wrapper).toBeTruthy()
})
})

describe('createSystem', () => {
it('should call the admin system store to create a new system', () => {
const adminSystemStore = useAdminSystemStore()
adminSystemStore.create.mockImplementation(() => Promise.resolve(1))

wrapper.vm.activeSystem.name = 'kiwi'

wrapper.vm.createSystem()

expect(adminSystemStore.create).toHaveBeenLastCalledWith({ name: 'kiwi' })
expect(wrapper.vm.showSystemAdd).toEqual(false)
})
})

describe('deleteSystem', () => {
it('should call deleteSystem on the admin system store', () => {
const adminSystemStore = useAdminSystemStore()

wrapper.vm.deleteSystem(1)

expect(adminSystemStore.deleteSystem).toHaveBeenCalledTimes(1)
expect(adminSystemStore.deleteSystem).toHaveBeenLastCalledWith(1)
})
})

describe('confirmDelete', () => {
it('should call confirm utility', () => {
wrapper.vm.confirmDelete({ name: 'kiwi', id: 1 })

expect(confirmAction).toHaveBeenCalled()
expect(confirmAction).toHaveBeenCalledWith('Are you sure you want to delete system kiwi? This will cause all related components to be deleted as well!', expect.any(Function))
})
})
53 changes: 53 additions & 0 deletions ui/test/vitest/__tests__/stores/adminSystemStore.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createPinia, setActivePinia } from 'pinia'
import { useAdminSystemStore } from 'src/stores/adminSystemStore'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { api } from 'src/boot/axios'
import { doPagedRequest } from 'src/utils/data'

vi.mock('src/utils/data', () => {
const doPagedRequest = vi.fn()

return { doPagedRequest }
})

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

vi.mock('src/boot/axios', () => {
const api = {
post: vi.fn().mockImplementation(() => Promise.resolve(true)),
delete: vi.fn().mockImplementation(() => Promise.resolve(true))
}

return { api }
})
})

describe('load', () => {
it('should load systems', async () => {
const adminSystemStore = useAdminSystemStore()
await adminSystemStore.load()

expect(doPagedRequest).toHaveBeenCalled()
})
})

describe('create', () => {
it('should make call to create system', async () => {
const adminSystemStore = useAdminSystemStore()
await adminSystemStore.create({ name: 'kiwi' })

expect(api.post).toHaveBeenCalled()
expect(api.post).toHaveBeenCalledWith('/systems', { name: 'kiwi' })
})
})

describe('deleteSystem', () => {
it('should make call to delete system', async () => {
const adminSystemStore = useAdminSystemStore()
await adminSystemStore.deleteSystem(1)

expect(api.delete).toHaveBeenCalled()
expect(api.delete).toHaveBeenCalledWith('/systems/1')
})
})
28 changes: 28 additions & 0 deletions ui/test/vitest/__tests__/stores/systemStore.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createPinia, setActivePinia } from 'pinia'
import { useSystemStore } from 'src/stores/systemStore'
import { beforeEach, describe, expect, it, vi } 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: 'kiwi' }] }))
}

return { api }
})
})

describe('load', () => {
it('should load systems for user', async () => {
const systemStore = useSystemStore()

await systemStore.load()

expect(api.get).toHaveBeenCalled()
expect(api.get).toHaveBeenCalledWith('/systems')
expect(systemStore.systems).toEqual([{ name: 'kiwi' }])
})
})

0 comments on commit 4943734

Please sign in to comment.