Fix #171: dedicated endpoint to list import jobs, updated front-end

This commit is contained in:
Eliot Berriot 2018-04-22 15:11:01 +02:00
commit 6a67bc6fac
No known key found for this signature in database
GPG key ID: DD6965E2476E5C27
8 changed files with 400 additions and 109 deletions

View file

@ -4,31 +4,80 @@
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div>
<div v-if="batch" class="ui vertical stripe segment">
<div :class="
['ui',
{'active': batch.status === 'pending'},
{'warning': batch.status === 'pending'},
{'error': batch.status === 'errored'},
{'success': batch.status === 'finished'},
'progress']">
<div class="bar" :style="progressBarStyle">
<div class="progress"></div>
<table class="ui very basic table">
<tbody>
<tr>
<td>
<strong>{{ $t('Import batch') }}</strong>
</td>
<td>
#{{ batch.id }}
</td>
</tr>
<tr>
<td>
<strong>{{ $t('Launch date') }}</strong>
</td>
<td>
<human-date :date="batch.creation_date"></human-date>
</td>
</tr>
<tr v-if="batch.user">
<td>
<strong>{{ $t('Submitted by') }}</strong>
</td>
<td>
<username :username="batch.user.username" />
</td>
</tr>
<tr v-if="stats">
<td><strong>{{ $t('Pending') }}</strong></td>
<td>{{ stats.pending }}</td>
</tr>
<tr v-if="stats">
<td><strong>{{ $t('Skipped') }}</strong></td>
<td>{{ stats.skipped }}</td>
</tr>
<tr v-if="stats">
<td><strong>{{ $t('Errored') }}</strong></td>
<td>{{ stats.errored }}</td>
</tr>
<tr v-if="stats">
<td><strong>{{ $t('Finished') }}</strong></td>
<td>{{ stats.finished }}/{{ stats.count}}</td>
</tr>
</tbody>
</table>
<div class="ui inline form">
<div class="fields">
<div class="ui field">
<label>{{ $t('Search') }}</label>
<input type="text" v-model="jobFilters.search" placeholder="Search by source..." />
</div>
<div class="ui field">
<label>{{ $t('Status') }}</label>
<select class="ui dropdown" v-model="jobFilters.status">
<option :value="null">{{ $t('Any') }}</option>
<option :value="'pending'">{{ $t('Pending') }}</option>
<option :value="'errored'">{{ $t('Errored') }}</option>
<option :value="'finished'">{{ $t('Success') }}</option>
<option :value="'skipped'">{{ $t('Skipped') }}</option>
</select>
</div>
</div>
<div v-if="batch.status === 'pending'" class="label">Importing {{ batch.jobs.length }} tracks...</div>
<div v-if="batch.status === 'finished'" class="label">Imported {{ batch.jobs.length }} tracks!</div>
</div>
<table class="ui unstackable table">
<table v-if="jobResult" class="ui unstackable table">
<thead>
<tr>
<i18next tag="th" path="Job ID"/>
<i18next tag="th" path="Recording MusicBrainz ID"/>
<i18next tag="th" path="Source"/>
<i18next tag="th" path="Status"/>
<i18next tag="th" path="Track"/>
<th>{{ $t('Job ID') }}</th>
<th>{{ $t('Recording MusicBrainz ID') }}</th>
<th>{{ $t('Source') }}</th>
<th>{{ $t('Status') }}</th>
<th>{{ $t('Track') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="job in batch.jobs">
<tr v-for="job in jobResult.results">
<td>{{ job.id }}</th>
<td>
<a :href="'https://www.musicbrainz.org/recording/' + job.mbid" target="_blank">{{ job.mbid }}</a>
@ -45,29 +94,64 @@
</td>
</tr>
</tbody>
<tfoot class="full-width">
<tr>
<th>
<pagination
v-if="jobResult && jobResult.results.length > 0"
@page-changed="selectPage"
:compact="true"
:current="jobFilters.page"
:paginate-by="jobFilters.paginateBy"
:total="jobResult.count"
></pagination>
</th>
<th v-if="jobResult && jobResult.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((jobFilters.page-1) * jobFilters.paginateBy) + 1 , end: ((jobFilters.page-1) * jobFilters.paginateBy) + jobResult.results.length, total: jobResult.count})}}
<th>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import axios from 'axios'
import logger from '@/logging'
const FETCH_URL = 'import-batches/'
import Pagination from '@/components/Pagination'
export default {
props: ['id'],
components: {
Pagination
},
data () {
return {
isLoading: true,
batch: null,
timeout: null
stats: null,
jobResult: null,
timeout: null,
jobFilters: {
status: null,
source: null,
search: '',
paginateBy: 25,
page: 1
}
}
},
created () {
this.fetchData()
let self = this
this.fetchData().then(() => {
self.fetchJobs()
self.fetchStats()
})
},
destroyed () {
if (this.timeout) {
@ -78,9 +162,9 @@ export default {
fetchData () {
var self = this
this.isLoading = true
let url = FETCH_URL + this.id + '/'
let url = 'import-batches/' + this.id + '/'
logger.default.debug('Fetching batch "' + this.id + '"')
axios.get(url).then((response) => {
return axios.get(url).then((response) => {
self.batch = response.data
self.isLoading = false
if (self.batch.status === 'pending') {
@ -90,21 +174,58 @@ export default {
)
}
})
}
},
computed: {
progress () {
return this.batch.jobs.filter(j => {
return j.status !== 'pending'
}).length * 100 / this.batch.jobs.length
},
progressBarStyle () {
return 'width: ' + parseInt(this.progress) + '%'
fetchStats () {
var self = this
let url = 'import-jobs/stats/'
axios.get(url, {params: {batch: self.id}}).then((response) => {
let old = self.stats
self.stats = response.data
self.isLoading = false
if (!_.isEqual(old, self.stats)) {
self.fetchJobs()
self.fetchData()
}
if (self.batch.status === 'pending') {
self.timeout = setTimeout(
self.fetchStats,
5000
)
}
})
},
fetchJobs () {
let params = {
batch: this.id,
page_size: this.jobFilters.paginateBy,
page: this.jobFilters.page,
q: this.jobFilters.search
}
if (this.jobFilters.status) {
params.status = this.jobFilters.status
}
if (this.jobFilters.source) {
params.source = this.jobFilters.source
}
let self = this
axios.get('import-jobs/', {params}).then((response) => {
self.jobResult = response.data
})
},
selectPage: function (page) {
this.jobFilters.page = page
}
},
watch: {
id () {
this.fetchData()
},
jobFilters: {
handler () {
this.fetchJobs()
},
deep: true
}
}
}

View file

@ -2,76 +2,144 @@
<div v-title="'Import Batches'">
<div class="ui vertical stripe segment">
<div v-if="isLoading" :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
<button
class="ui left floated labeled icon button"
@click="fetchData(previousLink)"
:disabled="!previousLink"><i class="left arrow icon"></i><i18next path="Previous"/></button>
<button
class="ui right floated right labeled icon button"
@click="fetchData(nextLink)"
:disabled="!nextLink"><i18next path="Next"/><i class="right arrow icon"></i></button>
<div class="ui inline form">
<div class="fields">
<div class="ui field">
<label>{{ $t('Search') }}</label>
<input type="text" v-model="filters.search" placeholder="Search by submitter, source..." />
</div>
<div class="ui field">
<label>{{ $t('Status') }}</label>
<select class="ui dropdown" v-model="filters.status">
<option :value="null">{{ $t('Any') }}</option>
<option :value="'pending'">{{ $t('Pending') }}</option>
<option :value="'errored'">{{ $t('Errored') }}</option>
<option :value="'finished'">{{ $t('Success') }}</option>
</select>
</div>
<div class="ui field">
<label>{{ $t('Import source') }}</label>
<select class="ui dropdown" v-model="filters.source">
<option :value="null">{{ $t('Any') }}</option>
<option :value="'shell'">{{ $t('CLI') }}</option>
<option :value="'api'">{{ $t('API') }}</option>
<option :value="'federation'">{{ $t('Federation') }}</option>
</select>
</div>
</div>
</div>
<div class="ui hidden clearing divider"></div>
<div class="ui hidden clearing divider"></div>
<table v-if="results.length > 0" class="ui unstackable table">
<table v-if="result && result.results.length > 0" class="ui unstackable table">
<thead>
<tr>
<i18next tag="th" path="ID"/>
<i18next tag="th" path="Launch date"/>
<i18next tag="th" path="Jobs"/>
<i18next tag="th" path="Status"/>
<th>{{ $t('ID') }}</th>
<th>{{ $t('Launch date') }}</th>
<th>{{ $t('Jobs') }}</th>
<th>{{ $t('Status') }}</th>
<th>{{ $t('Source') }}</th>
<th>{{ $t('Submitted by') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="result in results">
<td>{{ result.id }}</th>
<tr v-for="obj in result.results">
<td>{{ obj.id }}</th>
<td>
<router-link :to="{name: 'library.import.batches.detail', params: {id: result.id }}">
{{ result.creation_date }}
<router-link :to="{name: 'library.import.batches.detail', params: {id: obj.id }}">
<human-date :date="obj.creation_date"></human-date>
</router-link>
</td>
<td>{{ result.jobs.length }}</td>
<td>{{ obj.job_count }}</td>
<td>
<span
:class="['ui', {'yellow': result.status === 'pending'}, {'red': result.status === 'errored'}, {'green': result.status === 'finished'}, 'label']">{{ result.status }}</span>
</td>
</tr>
</tbody>
</table>
</div>
:class="['ui', {'yellow': obj.status === 'pending'}, {'red': obj.status === 'errored'}, {'green': obj.status === 'finished'}, 'label']">{{ obj.status }}
</span>
</td>
<td>{{ obj.source }}</td>
<td><template v-if="obj.submitted_by">{{ obj.submitted_by.username }}</template></td>
</tr>
</tbody>
<tfoot class="full-width">
<tr>
<th>
<pagination
v-if="result && result.results.length > 0"
@page-changed="selectPage"
:compact="true"
:current="filters.page"
:paginate-by="filters.paginateBy"
:total="result.count"
></pagination>
</th>
<th v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((filters.page-1) * filters.paginateBy) + 1 , end: ((filters.page-1) * filters.paginateBy) + result.results.length, total: result.count})}}
<th>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios'
import logger from '@/logging'
const BATCHES_URL = 'import-batches/'
import Pagination from '@/components/Pagination'
export default {
components: {},
components: {
Pagination
},
data () {
return {
results: [],
result: null,
isLoading: false,
nextLink: null,
previousLink: null
filters: {
status: null,
source: null,
search: '',
paginateBy: 25,
page: 1
}
}
},
created () {
this.fetchData(BATCHES_URL)
this.fetchData()
},
methods: {
fetchData (url) {
fetchData () {
let params = {
page_size: this.filters.paginateBy,
page: this.filters.page,
q: this.filters.search
}
if (this.filters.status) {
params.status = this.filters.status
}
if (this.filters.source) {
params.source = this.filters.source
}
var self = this
this.isLoading = true
logger.default.time('Loading import batches')
axios.get(url, {}).then((response) => {
self.results = response.data.results
self.nextLink = response.data.next
self.previousLink = response.data.previous
axios.get('import-batches/', {params}).then((response) => {
self.result = response.data
logger.default.timeEnd('Loading import batches')
self.isLoading = false
})
},
selectPage: function (page) {
this.filters.page = page
}
},
watch: {
filters: {
handler () {
this.fetchData()
},
deep: true
}
}
}