Skip to content

Commit

Permalink
Merge branch 'ui-improvements' of 'https://github.com/evamillan/grimo…
Browse files Browse the repository at this point in the history
…irelab-core'

Merges #34
Closes #34
  • Loading branch information
jjmerchante authored Feb 21, 2025
2 parents 2f8e4cc + 252d865 commit 868028d
Show file tree
Hide file tree
Showing 17 changed files with 507 additions and 80 deletions.
20 changes: 18 additions & 2 deletions src/grimoirelab/core/scheduler/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

import django_rq

from django.db.models import F

from rest_framework import (
filters,
generics,
pagination,
response,
Expand Down Expand Up @@ -137,9 +140,18 @@ def get_logs(self, obj):


class EventizerTaskList(generics.ListAPIView):
queryset = EventizerTask.objects.all().order_by('-scheduled_at')
serializer_class = EventizerTaskListSerializer
pagination_class = EventizerPaginator
filter_backends = [filters.OrderingFilter]
ordering_fields = ['scheduled_at', 'last_run']
ordering = [F('last_run').desc(nulls_first=True)]

def get_queryset(self):
queryset = EventizerTask.objects.all()
status = self.request.query_params.get('status')
if status is not None:
queryset = queryset.filter(status=status)
return queryset


class EventizerTaskDetail(generics.RetrieveAPIView):
Expand All @@ -155,7 +167,11 @@ class EventizerJobList(generics.ListAPIView):

def get_queryset(self):
task_id = self.kwargs['task_id']
return get_registered_task_model('eventizer')[1].objects.filter(task__uuid=task_id)
queryset = get_registered_task_model('eventizer')[1].objects.filter(task__uuid=task_id).order_by('-scheduled_at')
status = self.request.query_params.get('status')
if status is not None:
queryset = queryset.filter(status=status)
return queryset


class EventizerJobDetail(generics.RetrieveAPIView):
Expand Down
53 changes: 50 additions & 3 deletions ui/src/components/JobList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,35 @@
{{ count }}
</v-chip>
</h2>

<v-tabs
v-model="tab"
:items="tabs"
align-tabs="left"
class="mb-4"
color="primary"
height="36"
slider-color="primary"
@update:model-value="$emit('update:filters', { status: $event, page: 1 })"
>
<template #tab="{ item }">
<v-tab :text="item.text" :value="item.value" class="text-none text-subtitle-2"></v-tab>
</template>
</v-tabs>

<div v-if="loading" class="d-flex justify-center pa-4">
<v-progress-circular class="mx-auto" color="primary" indeterminate />
</div>

<v-empty-state
v-else-if="!loading && count === 0"
icon="mdi-magnify"
title="No results found"
size="52"
></v-empty-state>

<status-card
v-else
v-for="job in jobs"
:key="job.uuid"
:status="job.status"
Expand Down Expand Up @@ -47,7 +75,7 @@
:length="pages"
color="primary"
density="comfortable"
@update:model-value="$emit('update:page', $event)"
@update:model-value="$emit('update:filters', { page: $event, status: tab })"
/>
</div>
</template>
Expand All @@ -58,7 +86,7 @@ import StatusCard from '@/components/StatusCard.vue'
export default {
name: 'JobList',
components: { StatusCard },
emits: ['update:page'],
emits: ['update:filters'],
props: {
jobs: {
type: Array,
Expand All @@ -71,21 +99,40 @@ export default {
pages: {
type: Number,
required: true
},
loading: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
page: 1
page: 1,
tab: 'all',
tabs: [
{ text: 'All', value: 'all' },
{ text: 'Failed', value: 5 },
{ text: 'Completed', value: 4 }
]
}
},
methods: {
formatDate,
getDuration
},
watch: {
tab() {
this.page = 1
}
}
}
</script>
<style lang="scss" scoped>
.v-card .v-card-title {
line-height: 1.7rem;
}
.v-tab.v-tab.v-btn {
min-width: 0;
}
</style>
107 changes: 107 additions & 0 deletions ui/src/components/OrderSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<v-select
v-model="selectedValue"
:items="options"
:list-props="{ nav: true }"
:menu-props="{ offsetY: true, bottom: true, nudgeTop: 8 }"
density="compact"
label="Order by"
class="select--segmented"
variant="outlined"
attach
single-line
@update:model-value="emitValue"
>
<template #prepend>
<v-tooltip location="bottom">
<template #activator="{ props }">
<v-btn
v-bind="props"
variant="text"
height="32"
@click="changeOrder"
@keyup.enter="changeOrder"
>
<v-icon small>
{{ icon }}
</v-icon>
</v-btn>
</template>
<span> {{ descending ? 'Descending ' : 'Ascending ' }} order </span>
</v-tooltip>
</template>
</v-select>
</template>
<script>
export default {
name: 'OrderSelector',
emits: ['update:value'],
props: {
/** Objects should have title and value properties */
options: {
type: Array,
required: true
},
default: {
type: String,
required: false,
default: undefined
}
},
data() {
return {
descending: true,
selectedValue: this.default
}
},
computed: {
value() {
return `${this.descending ? '-' : ''}${this.selectedValue}`
},
icon() {
return this.descending ? 'mdi-sort-descending' : 'mdi-sort-ascending'
}
},
methods: {
emitValue() {
this.$emit('update:value', this.value)
},
changeOrder() {
this.descending = !this.descending
if (this.selectedValue) {
this.emitValue()
}
}
}
}
</script>
<style lang="scss" scoped>
.select--segmented {
max-width: fit-content;
.v-select__selection {
margin-top: 0;
height: 37px;
}
:deep(.v-field) {
font-size: 0.875rem;
font-weight: 500;
}
:deep(.v-input__prepend) {
border: solid rgba(0, 0, 0, 0.15);
border-width: 1px 0 1px 1px;
border-radius: 4px 0 0 4px;
margin: 0;
}
:deep(.v-input__prepend) + .v-input__control > .v-field--center-affix {
border-radius: 0 4px 4px 0;
}
:deep(.v-field__outline) {
--v-field-border-opacity: 0.15;
}
}
</style>
35 changes: 35 additions & 0 deletions ui/src/components/StatusIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<v-icon :color="status" :size="size">
{{ icon }}
</v-icon>
</template>
<script>
export default {
name: 'StatusIcon',
props: {
status: {
type: String,
required: true
},
size: {
type: String,
required: false,
default: 'small'
}
},
computed: {
icon() {
switch (this.status) {
case 'completed':
return 'mdi-check'
case 'failed':
return 'mdi-alert-circle-outline'
case 'running':
return 'mdi-sync'
default:
return 'mdi-clock-outline'
}
}
}
}
</script>
29 changes: 15 additions & 14 deletions ui/src/components/TaskCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
size="small"
start
>
{{ statusIcon }}
<status-icon :status="status" size="x-small" />
</v-icon>
Last run {{ lastRunDate }}
</v-card-subtitle>
Expand All @@ -42,17 +42,25 @@
{{ formattedInterval }}
</span>
</p>
<p v-if="failures" class="pb-2 text-body-2">
<v-icon color="failed" size="small" start> mdi-alert-circle-outline </v-icon>
<span class="font-weight-medium">
{{ failures }}
</span>
failure{{ failures > 1 ? 's' : '' }}
</p>
</v-col>
</v-row>
</status-card>
</template>
<script>
import { formatDate } from '@/utils/dates'
import StatusCard from '@/components/StatusCard.vue'
import StatusIcon from './StatusIcon.vue'
export default {
name: 'TaskCard',
components: { StatusCard },
components: { StatusCard, StatusIcon },
props: {
age: {
type: [Number, String],
Expand Down Expand Up @@ -98,6 +106,11 @@ export default {
type: String,
required: false,
default: null
},
failures: {
type: Number,
required: false,
default: null
}
},
computed: {
Expand All @@ -111,18 +124,6 @@ export default {
return `${this.interval} seconds`
}
},
statusIcon() {
switch (this.status) {
case 'completed':
return 'mdi-check'
case 'failed':
return 'mdi-close'
case 'running':
return 'mdi-sync'
default:
return 'mdi-calendar'
}
},
lastRunDate() {
if (this.lastExecution) {
return formatDate(this.lastExecution)
Expand Down
Loading

0 comments on commit 868028d

Please sign in to comment.