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

[4.x] Add separate screen for completed jobs #767

Merged
merged 5 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
- Dropped support for Laravel 5.8 ([8216c34](https://github.com/laravel/horizon/commit/8216c34736564998380f97252c117716020aee0a))
- Bumped minimum Symfony dependencies to 4.3 ([2eb9d6d](https://github.com/laravel/horizon/commit/2eb9d6d77acd5e8b42da0a8146c9e8d8105fbd8a))

## [v3.7.0 (2020-02-14)](https://github.com/laravel/horizon/compare/v3.6.1...v3.7.0)

### Added
- Ability to see if a job is delayed ([#755](https://github.com/laravel/horizon/pull/755))
- Allow trimming of completed jobs ([#720](https://github.com/laravel/horizon/pull/720))


## [v3.6.1 (2020-02-12)](https://github.com/laravel/horizon/compare/v3.6.0...v3.6.1)

Expand Down
2 changes: 2 additions & 0 deletions config/horizon.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@

'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
Expand Down
10,943 changes: 10,941 additions & 2 deletions public/app-dark.css

Large diffs are not rendered by default.

10,943 changes: 10,941 additions & 2 deletions public/app.css

Large diffs are not rendered by default.

78,618 changes: 78,617 additions & 1 deletion public/app.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions public/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"/app.js": "/app.js?id=0d279600e8f8bb811551",
"/app.css": "/app.css?id=b95d548aba488172f4b4",
"/app-dark.css": "/app-dark.css?id=b5b064c2f5a4b673a1c5"
"/app.js": "/app.js?id=fff0dfc2f44ccbc46d39",
"/app.css": "/app.css?id=f4bfed2b51f0e5e0dcb6",
"/app-dark.css": "/app-dark.css?id=9ef119e56b37b9067363"
}
18 changes: 15 additions & 3 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import Routes from './routes';
import VueRouter from 'vue-router';
import VueJsonPretty from 'vue-json-pretty';

require('bootstrap');
window.Popper = require('popper.js').default;

try {
window.$ = window.jQuery = require('jquery');

require('bootstrap');
} catch (e) {}

let token = document.head.querySelector('meta[name="csrf-token"]');

Expand All @@ -17,8 +23,6 @@ if (token) {

Vue.use(VueRouter);

window.Popper = require('popper.js').default;

Vue.prototype.$http = axios.create();

window.Horizon.basePath = '/' + window.Horizon.path;
Expand All @@ -41,6 +45,14 @@ Vue.component('alert', require('./components/Alert.vue').default);

Vue.mixin(Base);

Vue.directive('tooltip', function(el, binding) {
$(el).tooltip({
title: binding.value,
placement: binding.arg,
trigger: 'hover',
});
});

new Vue({
el: '#horizon',

Expand Down
2 changes: 0 additions & 2 deletions resources/js/components/Alert.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script type="text/ecmascript-6">
import $ from 'jquery';

export default {
props: ['type', 'message', 'autoClose', 'confirmationProceed', 'confirmationCancel'],

Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/Stacktrace.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script type="text/ecmascript-6">
import _ from "lodash"
import _take from "lodash/take"

export default {
props: ['trace'],
Expand All @@ -16,7 +16,7 @@

computed: {
lines(){
return this.showAll ? _.take(this.trace, 1000) : _.take(this.trace, this.minimumLines);
return this.showAll ? _take(this.trace, 1000) : _take(this.trace, this.minimumLines);
}
}
}
Expand All @@ -38,4 +38,4 @@

<style scoped>

</style>
</style>
14 changes: 10 additions & 4 deletions resources/js/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,20 @@ export default [
},

{
path: '/recent-jobs',
name: 'recent-jobs',
path: '/jobs/:type',
name: 'jobs',
component: require('./screens/recentJobs/index').default,
},

{
path: '/recent-jobs/:jobId',
name: 'recent-jobs-preview',
path: '/jobs/pending/:jobId',
name: 'pending-jobs-preview',
component: require('./screens/recentJobs/job').default,
},

{
path: '/jobs/completed/:jobId',
name: 'completed-jobs-preview',
component: require('./screens/recentJobs/job').default,
},

Expand Down
9 changes: 6 additions & 3 deletions resources/js/screens/failedJobs/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,17 @@

<tr v-for="job in jobs" :key="job.id">
<td>
<span v-if="job.status != 'failed'" :title="job.name">{{jobBaseName(job.name)}}</span>
<router-link v-if="job.status === 'failed'" :title="job.name" :to="{ name: 'failed-jobs-preview', params: { jobId: job.id }}">
<router-link :title="job.name" :to="{ name: 'failed-jobs-preview', params: { jobId: job.id }}">
{{ jobBaseName(job.name) }}
</router-link>
<br>

<small class="text-muted">
Queue: {{job.queue}} | Attempts: {{ job.payload.attempts }} | Tags: {{ job.payload.tags && job.payload.tags.length ? job.payload.tags.join(', ') : '' }}
Queue: {{job.queue}}
| Attempts: {{ job.payload.attempts }}
<span v-if="job.payload.tags.length">
| Tags: {{ job.payload.tags && job.payload.tags.length ? job.payload.tags.join(', ') : '' }}
</span>
</small>
</td>

Expand Down
2 changes: 0 additions & 2 deletions resources/js/screens/monitoring/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script type="text/ecmascript-6">
import $ from 'jquery';

export default {
/**
* The component's data.
Expand Down
86 changes: 29 additions & 57 deletions resources/js/screens/recentJobs/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script type="text/ecmascript-6">
import $ from 'jquery';
import JobRow from './job-row';

export default {
/**
Expand All @@ -17,13 +17,22 @@
};
},

/**
* Components
*/
components: {
JobRow,
},

/**
* Prepare the component.
*/
mounted() {
document.title = "Horizon - Recent Jobs";
document.title = this.$route.params.type == 'pending'
? 'Horizon - Pending Jobs'
: 'Horizon - Completed Jobs';

this.loadJobs(this.$route.params.tag);
this.loadJobs();

this.refreshJobsPeriodically();
},
Expand All @@ -43,7 +52,7 @@
'$route'() {
this.page = 1;

this.loadJobs(this.$route.params.tag);
this.loadJobs();
}
},

Expand All @@ -57,7 +66,7 @@
this.ready = false;
}

this.$http.get(Horizon.basePath + '/api/jobs/recent?starting_at=' + starting + '&limit=' + this.perPage)
this.$http.get(Horizon.basePath + '/api/jobs/' + this.$route.params.type + '?starting_at=' + starting + '&limit=' + this.perPage)
.then(response => {
if (!this.$root.autoLoadsNewEntries && refreshing && this.jobs.length && _.first(response.data.jobs).id !== _.first(this.jobs).id) {
this.hasNewEntries = true;
Expand Down Expand Up @@ -129,7 +138,8 @@
<div>
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between">
<h5>Recent Jobs</h5>
<h5 v-if="$route.params.type == 'pending'">Pending Jobs</h5>
<h5 v-if="$route.params.type == 'completed'">Completed Jobs</h5>
</div>

<div v-if="!ready"
Expand All @@ -152,63 +162,25 @@
<thead>
<tr>
<th>Job</th>
<th>Queued At</th>
<th>Runtime</th>
<th class="text-right">Status</th>
<th v-if="$route.params.type=='pending'" class="text-right">Queued At</th>
<th v-if="$route.params.type=='completed'">Queued At</th>
<th v-if="$route.params.type=='completed'">Completed At</th>
<th v-if="$route.params.type=='completed'" class="text-right">Runtime</th>
</tr>
</thead>

<tbody>
<tr v-if="hasNewEntries" key="newEntries" class="dontanimate">
<td colspan="100" class="text-center card-bg-secondary py-1">
<small><a href="#" v-on:click.prevent="loadNewEntries" v-if="!loadingNewEntries">Load New
Entries</a></small>
<tr v-if="hasNewEntries" key="newEntries" class="dontanimate">
<td colspan="100" class="text-center card-bg-secondary py-1">
<small><a href="#" v-on:click.prevent="loadNewEntries" v-if="!loadingNewEntries">Load New
Entries</a></small>

<small v-if="loadingNewEntries">Loading...</small>
</td>
</tr>
<small v-if="loadingNewEntries">Loading...</small>
</td>
</tr>

<tr v-for="job in jobs" :key="job.id">
<td>
<span v-if="job.status != 'failed'" :title="job.name">{{jobBaseName(job.name)}}</span>
<router-link v-if="job.status === 'failed'" :title="job.name" :to="{ name: 'failed-jobs-preview', params: { jobId: job.id }}">
{{ jobBaseName(job.name) }}
</router-link><br>

<small class="text-muted">
<router-link :to="{name: 'recent-jobs-preview', params: {jobId: job.id}}">View detail</router-link> |
Queue: {{job.queue}} | Attempts: {{ job.payload.attempts }}
<span v-if="job.payload.tags.length">
| Tags: {{ job.payload.tags && job.payload.tags.length ? job.payload.tags.slice(0,3).join(', ') : '' }}<span v-if="job.payload.tags.length > 3"> ({{ job.payload.tags.length - 3 }} more)</span>
</span>
</small>
</td>
<td class="table-fit">
{{ readableTimestamp(job.payload.pushedAt) }}
</td>

<td class="table-fit">
<span>{{ job.completed_at ? (job.completed_at - job.reserved_at).toFixed(2)+'s' : '-' }}</span>
</td>

<td class="text-right table-fit">
<svg v-if="job.status == 'completed'" class="fill-success" viewBox="0 0 20 20" style="width: 1.5rem; height: 1.5rem;">
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM6.7 9.29L9 11.6l4.3-4.3 1.4 1.42L9 14.4l-3.7-3.7 1.4-1.42z"></path>
</svg>

<svg v-if="job.status == 'pending'" class="fill-warning" viewBox="0 0 20 20" style="width: 1.5rem; height: 1.5rem;">
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM7 6h2v8H7V6zm4 0h2v8h-2V6z"/>
</svg>

<svg v-if="job.status == 'reserved'" class="fill-primary spin" viewBox="0 0 512 512" style="width: 1.5rem; height: 1.5rem;">
<path d="M424.5,216.5h-15.2c-12.4,0-22.8-10.7-22.8-23.4c0-6.4,2.7-12.2,7.5-16.5l9.8-9.6c9.7-9.6,9.7-25.3,0-34.9l-22.3-22.1 c-4.4-4.4-10.9-7-17.5-7c-6.6,0-13,2.6-17.5,7l-9.4,9.4c-4.5,5-10.5,7.7-17,7.7c-12.8,0-23.5-10.4-23.5-22.7V89.1 c0-13.5-10.9-25.1-24.5-25.1h-30.4c-13.6,0-24.4,11.5-24.4,25.1v15.2c0,12.3-10.7,22.7-23.5,22.7c-6.4,0-12.3-2.7-16.6-7.4l-9.7-9.6 c-4.4-4.5-10.9-7-17.5-7s-13,2.6-17.5,7L110,132c-9.6,9.6-9.6,25.3,0,34.8l9.4,9.4c5,4.5,7.8,10.5,7.8,16.9 c0,12.8-10.4,23.4-22.8,23.4H89.2c-13.7,0-25.2,10.7-25.2,24.3V256v15.2c0,13.5,11.5,24.3,25.2,24.3h15.2 c12.4,0,22.8,10.7,22.8,23.4c0,6.4-2.8,12.4-7.8,16.9l-9.4,9.3c-9.6,9.6-9.6,25.3,0,34.8l22.3,22.2c4.4,4.5,10.9,7,17.5,7 c6.6,0,13-2.6,17.5-7l9.7-9.6c4.2-4.7,10.2-7.4,16.6-7.4c12.8,0,23.5,10.4,23.5,22.7v15.2c0,13.5,10.8,25.1,24.5,25.1h30.4 c13.6,0,24.4-11.5,24.4-25.1v-15.2c0-12.3,10.7-22.7,23.5-22.7c6.4,0,12.4,2.8,17,7.7l9.4,9.4c4.5,4.4,10.9,7,17.5,7 c6.6,0,13-2.6,17.5-7l22.3-22.2c9.6-9.6,9.6-25.3,0-34.9l-9.8-9.6c-4.8-4.3-7.5-10.2-7.5-16.5c0-12.8,10.4-23.4,22.8-23.4h15.2 c13.6,0,23.3-10.7,23.3-24.3V256v-15.2C447.8,227.2,438.1,216.5,424.5,216.5z M336.8,256L336.8,256c0,44.1-35.7,80-80,80 c-44.3,0-80-35.9-80-80l0,0l0,0c0-44.1,35.7-80,80-80C301.1,176,336.8,211.9,336.8,256L336.8,256z"></path>
</svg>

<svg v-if="job.status == 'failed'" class="fill-danger" viewBox="0 0 20 20" style="width: 1.5rem; height: 1.5rem;">
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z"/>
</svg>
</td>
</tr>
<tr v-for="job in jobs" :key="job.id" :job="job" is="job-row">
</tr>
</tbody>
</table>

Expand Down
65 changes: 65 additions & 0 deletions resources/js/screens/recentJobs/job-row.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template>
<tr>
<td>
<router-link :title="job.name" :to="{ name: $route.params.type+'-jobs-preview', params: { jobId: job.id }}">
{{ jobBaseName(job.name) }}
</router-link>

<small class="badge badge-secondary badge-sm"
v-tooltip:top="`Delayed for ${delayed}`"
v-if="delayed && (job.status == 'reserved' || job.status == 'pending')">
Delayed
</small>

<br>

<small class="text-muted">
Queue: {{job.queue}}

<span v-if="job.payload.tags.length">
| Tags: {{ job.payload.tags && job.payload.tags.length ? job.payload.tags.slice(0,3).join(', ') : '' }}<span v-if="job.payload.tags.length > 3"> ({{ job.payload.tags.length - 3 }} more)</span>
</span>
</small>
</td>

<td class="table-fit">
{{ readableTimestamp(job.payload.pushedAt) }}
</td>

<td v-if="$route.params.type=='completed'" class="table-fit">
{{ readableTimestamp(job.completed_at) }}
</td>

<td v-if="$route.params.type=='completed'" class="table-fit">
<span>{{ job.completed_at ? (job.completed_at - job.reserved_at).toFixed(2)+'s' : '-' }}</span>
</td>
</tr>
</template>

<script type="text/ecmascript-6">
import phpunserialize from 'phpunserialize'
import moment from 'moment-timezone';

export default {
props: {
job: {
type: Object,
required: true
}
},

computed: {
unserialized() {
return phpunserialize(this.job.payload.data.command);
},

delayed() {
if (this.unserialized && this.unserialized.delay){
return moment.utc(this.unserialized.delay.date).fromNow(true);
}

return null;
},
},
}
</script>
Loading