diff options
-rw-r--r-- | client/src/app/+admin/system/jobs/job.service.ts | 7 | ||||
-rw-r--r-- | client/src/app/+admin/system/jobs/jobs.component.html | 20 | ||||
-rw-r--r-- | client/src/app/+admin/system/jobs/jobs.component.scss | 18 | ||||
-rw-r--r-- | client/src/app/+admin/system/jobs/jobs.component.ts | 39 | ||||
-rw-r--r-- | client/src/types/job-type-client.type.ts | 3 | ||||
-rw-r--r-- | server/controllers/api/jobs.ts | 11 | ||||
-rw-r--r-- | server/helpers/custom-validators/jobs.ts | 10 | ||||
-rw-r--r-- | server/lib/job-queue/job-queue.ts | 26 | ||||
-rw-r--r-- | server/middlewares/validators/jobs.ts | 12 | ||||
-rw-r--r-- | server/tests/api/check-params/jobs.ts | 12 | ||||
-rw-r--r-- | server/tests/api/server/handle-down.ts | 9 | ||||
-rw-r--r-- | server/tests/api/server/jobs.ts | 50 | ||||
-rw-r--r-- | server/tests/real-world/real-world.ts | 9 | ||||
-rw-r--r-- | shared/extra-utils/server/jobs.ts | 46 | ||||
-rw-r--r-- | shared/models/server/job.model.ts | 6 |
15 files changed, 220 insertions, 58 deletions
diff --git a/client/src/app/+admin/system/jobs/job.service.ts b/client/src/app/+admin/system/jobs/job.service.ts index 1daae8f03..120144dff 100644 --- a/client/src/app/+admin/system/jobs/job.service.ts +++ b/client/src/app/+admin/system/jobs/job.service.ts | |||
@@ -3,11 +3,12 @@ import { HttpClient, HttpParams } from '@angular/common/http' | |||
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { SortMeta } from 'primeng/api' | 4 | import { SortMeta } from 'primeng/api' |
5 | import { Observable } from 'rxjs' | 5 | import { Observable } from 'rxjs' |
6 | import { ResultList } from '../../../../../../shared' | 6 | import { JobType, ResultList } from '../../../../../../shared' |
7 | import { JobState } from '../../../../../../shared/models' | 7 | import { JobState } from '../../../../../../shared/models' |
8 | import { Job } from '../../../../../../shared/models/server/job.model' | 8 | import { Job } from '../../../../../../shared/models/server/job.model' |
9 | import { environment } from '../../../../environments/environment' | 9 | import { environment } from '../../../../environments/environment' |
10 | import { RestExtractor, RestPagination, RestService } from '../../../shared' | 10 | import { RestExtractor, RestPagination, RestService } from '../../../shared' |
11 | import { JobTypeClient } from '../../../../types/job-type-client.type' | ||
11 | 12 | ||
12 | @Injectable() | 13 | @Injectable() |
13 | export class JobService { | 14 | export class JobService { |
@@ -19,10 +20,12 @@ export class JobService { | |||
19 | private restExtractor: RestExtractor | 20 | private restExtractor: RestExtractor |
20 | ) {} | 21 | ) {} |
21 | 22 | ||
22 | getJobs (state: JobState, pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> { | 23 | getJobs (state: JobState, jobType: JobTypeClient, pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> { |
23 | let params = new HttpParams() | 24 | let params = new HttpParams() |
24 | params = this.restService.addRestGetParams(params, pagination, sort) | 25 | params = this.restService.addRestGetParams(params, pagination, sort) |
25 | 26 | ||
27 | if (jobType !== 'all') params = params.append('jobType', jobType) | ||
28 | |||
26 | return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + '/' + state, { params }) | 29 | return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + '/' + state, { params }) |
27 | .pipe( | 30 | .pipe( |
28 | map(res => { | 31 | map(res => { |
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html index 7ed1888e2..cd26257dd 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.html +++ b/client/src/app/+admin/system/jobs/jobs.component.html | |||
@@ -1,10 +1,22 @@ | |||
1 | <div class="admin-sub-header"> | 1 | <div class="admin-sub-header"> |
2 | <div i18n class="form-sub-title">Jobs list</div> | 2 | <div i18n class="form-sub-title">Jobs list</div> |
3 | 3 | ||
4 | <div class="peertube-select-container"> | 4 | <div class="select-filter-block"> |
5 | <select [(ngModel)]="jobState" (ngModelChange)="onJobStateChanged()"> | 5 | <label for="jobType">Job type</label> |
6 | <option *ngFor="let state of jobStates" [value]="state">{{ state }}</option> | 6 | <div class="peertube-select-container"> |
7 | </select> | 7 | <select id="jobType" name="jobType" [(ngModel)]="jobType" (ngModelChange)="onJobStateOrTypeChanged()"> |
8 | <option *ngFor="let jobType of jobTypes" [value]="jobType">{{ jobType }}</option> | ||
9 | </select> | ||
10 | </div> | ||
11 | </div> | ||
12 | |||
13 | <div class="select-filter-block"> | ||
14 | <label for="jobState">Job state</label> | ||
15 | <div class="peertube-select-container"> | ||
16 | <select id="jobState" name="jobState" [(ngModel)]="jobState" (ngModelChange)="onJobStateOrTypeChanged()"> | ||
17 | <option *ngFor="let state of jobStates" [value]="state">{{ state }}</option> | ||
18 | </select> | ||
19 | </div> | ||
8 | </div> | 20 | </div> |
9 | </div> | 21 | </div> |
10 | 22 | ||
diff --git a/client/src/app/+admin/system/jobs/jobs.component.scss b/client/src/app/+admin/system/jobs/jobs.component.scss index ab05f1982..ccc0b35ca 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.scss +++ b/client/src/app/+admin/system/jobs/jobs.component.scss | |||
@@ -1,8 +1,22 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .peertube-select-container { | 4 | .admin-sub-header { |
5 | @include peertube-select-container(auto); | 5 | align-items: flex-end; |
6 | |||
7 | .select-filter-block { | ||
8 | &:not(:last-child) { | ||
9 | margin-right: 10px; | ||
10 | } | ||
11 | |||
12 | label { | ||
13 | margin-bottom: 2px; | ||
14 | } | ||
15 | |||
16 | .peertube-select-container { | ||
17 | @include peertube-select-container(auto); | ||
18 | } | ||
19 | } | ||
6 | } | 20 | } |
7 | 21 | ||
8 | pre { | 22 | pre { |
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts index b24353ca6..95ee17023 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.ts +++ b/client/src/app/+admin/system/jobs/jobs.component.ts | |||
@@ -2,11 +2,12 @@ import { Component, OnInit } from '@angular/core' | |||
2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/api' | 4 | import { SortMeta } from 'primeng/api' |
5 | import { Job } from '../../../../../../shared/index' | 5 | import { Job, JobType } from '../../../../../../shared/index' |
6 | import { JobState } from '../../../../../../shared/models' | 6 | import { JobState } from '../../../../../../shared/models' |
7 | import { RestPagination, RestTable } from '../../../shared' | 7 | import { RestPagination, RestTable } from '../../../shared' |
8 | import { JobService } from './job.service' | 8 | import { JobService } from './job.service' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { JobTypeClient } from '../../../../types/job-type-client.type' | ||
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
12 | selector: 'my-jobs', | 13 | selector: 'my-jobs', |
@@ -15,9 +16,26 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
15 | }) | 16 | }) |
16 | export class JobsComponent extends RestTable implements OnInit { | 17 | export class JobsComponent extends RestTable implements OnInit { |
17 | private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state' | 18 | private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state' |
19 | private static JOB_STATE_LOCAL_STORAGE_TYPE = 'jobs-list-type' | ||
18 | 20 | ||
19 | jobState: JobState = 'waiting' | 21 | jobState: JobState = 'waiting' |
20 | jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] | 22 | jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] |
23 | |||
24 | jobType: JobTypeClient = 'all' | ||
25 | jobTypes: JobTypeClient[] = [ | ||
26 | 'all', | ||
27 | 'activitypub-follow', | ||
28 | 'activitypub-http-broadcast', | ||
29 | 'activitypub-http-fetcher', | ||
30 | 'activitypub-http-unicast', | ||
31 | 'email', | ||
32 | 'video-transcoding', | ||
33 | 'video-file-import', | ||
34 | 'video-import', | ||
35 | 'videos-views', | ||
36 | 'activitypub-refresher' | ||
37 | ] | ||
38 | |||
21 | jobs: Job[] = [] | 39 | jobs: Job[] = [] |
22 | totalRecords: number | 40 | totalRecords: number |
23 | rowsPerPage = 10 | 41 | rowsPerPage = 10 |
@@ -33,20 +51,20 @@ export class JobsComponent extends RestTable implements OnInit { | |||
33 | } | 51 | } |
34 | 52 | ||
35 | ngOnInit () { | 53 | ngOnInit () { |
36 | this.loadJobState() | 54 | this.loadJobStateAndType() |
37 | this.initialize() | 55 | this.initialize() |
38 | } | 56 | } |
39 | 57 | ||
40 | onJobStateChanged () { | 58 | onJobStateOrTypeChanged () { |
41 | this.pagination.start = 0 | 59 | this.pagination.start = 0 |
42 | 60 | ||
43 | this.loadData() | 61 | this.loadData() |
44 | this.saveJobState() | 62 | this.saveJobStateAndType() |
45 | } | 63 | } |
46 | 64 | ||
47 | protected loadData () { | 65 | protected loadData () { |
48 | this.jobsService | 66 | this.jobsService |
49 | .getJobs(this.jobState, this.pagination, this.sort) | 67 | .getJobs(this.jobState, this.jobType, this.pagination, this.sort) |
50 | .subscribe( | 68 | .subscribe( |
51 | resultList => { | 69 | resultList => { |
52 | this.jobs = resultList.data | 70 | this.jobs = resultList.data |
@@ -57,13 +75,16 @@ export class JobsComponent extends RestTable implements OnInit { | |||
57 | ) | 75 | ) |
58 | } | 76 | } |
59 | 77 | ||
60 | private loadJobState () { | 78 | private loadJobStateAndType () { |
61 | const result = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE) | 79 | const state = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE) |
80 | if (state) this.jobState = state as JobState | ||
62 | 81 | ||
63 | if (result) this.jobState = result as JobState | 82 | const type = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE) |
83 | if (type) this.jobType = type as JobType | ||
64 | } | 84 | } |
65 | 85 | ||
66 | private saveJobState () { | 86 | private saveJobStateAndType () { |
67 | peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState) | 87 | peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState) |
88 | peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE, this.jobType) | ||
68 | } | 89 | } |
69 | } | 90 | } |
diff --git a/client/src/types/job-type-client.type.ts b/client/src/types/job-type-client.type.ts new file mode 100644 index 000000000..7d51f1db2 --- /dev/null +++ b/client/src/types/job-type-client.type.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { JobType } from '@shared/models' | ||
2 | |||
3 | export type JobTypeClient = 'all' | JobType | ||
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts index 1fa662349..05320311e 100644 --- a/server/controllers/api/jobs.ts +++ b/server/controllers/api/jobs.ts | |||
@@ -24,7 +24,7 @@ jobsRouter.get('/:state', | |||
24 | jobsSortValidator, | 24 | jobsSortValidator, |
25 | setDefaultSort, | 25 | setDefaultSort, |
26 | setDefaultPagination, | 26 | setDefaultPagination, |
27 | asyncMiddleware(listJobsValidator), | 27 | listJobsValidator, |
28 | asyncMiddleware(listJobs) | 28 | asyncMiddleware(listJobs) |
29 | ) | 29 | ) |
30 | 30 | ||
@@ -39,8 +39,15 @@ export { | |||
39 | async function listJobs (req: express.Request, res: express.Response) { | 39 | async function listJobs (req: express.Request, res: express.Response) { |
40 | const state = req.params.state as JobState | 40 | const state = req.params.state as JobState |
41 | const asc = req.query.sort === 'createdAt' | 41 | const asc = req.query.sort === 'createdAt' |
42 | const jobType = req.query.jobType | ||
42 | 43 | ||
43 | const jobs = await JobQueue.Instance.listForApi(state, req.query.start, req.query.count, asc) | 44 | const jobs = await JobQueue.Instance.listForApi({ |
45 | state, | ||
46 | start: req.query.start, | ||
47 | count: req.query.count, | ||
48 | asc, | ||
49 | jobType | ||
50 | }) | ||
44 | const total = await JobQueue.Instance.count(state) | 51 | const total = await JobQueue.Instance.count(state) |
45 | 52 | ||
46 | const result: ResultList<any> = { | 53 | const result: ResultList<any> = { |
diff --git a/server/helpers/custom-validators/jobs.ts b/server/helpers/custom-validators/jobs.ts index 1cc6e6912..dd33e85a3 100644 --- a/server/helpers/custom-validators/jobs.ts +++ b/server/helpers/custom-validators/jobs.ts | |||
@@ -1,14 +1,20 @@ | |||
1 | import { JobState } from '../../../shared/models' | 1 | import { JobState } from '../../../shared/models' |
2 | import { exists } from './misc' | 2 | import { exists } from './misc' |
3 | import { jobTypes } from '@server/lib/job-queue/job-queue' | ||
3 | 4 | ||
4 | const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] | 5 | const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] |
5 | 6 | ||
6 | function isValidJobState (value: JobState) { | 7 | function isValidJobState (value: JobState) { |
7 | return exists(value) && jobStates.indexOf(value) !== -1 | 8 | return exists(value) && jobStates.includes(value) |
9 | } | ||
10 | |||
11 | function isValidJobType (value: any) { | ||
12 | return exists(value) && jobTypes.includes(value) | ||
8 | } | 13 | } |
9 | 14 | ||
10 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
11 | 16 | ||
12 | export { | 17 | export { |
13 | isValidJobState | 18 | isValidJobState, |
19 | isValidJobType | ||
14 | } | 20 | } |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index 3c810da98..ec601e9ea 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -121,11 +121,20 @@ class JobQueue { | |||
121 | return queue.add(obj.payload, jobArgs) | 121 | return queue.add(obj.payload, jobArgs) |
122 | } | 122 | } |
123 | 123 | ||
124 | async listForApi (state: JobState, start: number, count: number, asc?: boolean): Promise<Bull.Job[]> { | 124 | async listForApi (options: { |
125 | state: JobState, | ||
126 | start: number, | ||
127 | count: number, | ||
128 | asc?: boolean, | ||
129 | jobType: JobType | ||
130 | }): Promise<Bull.Job[]> { | ||
131 | const { state, start, count, asc, jobType } = options | ||
125 | let results: Bull.Job[] = [] | 132 | let results: Bull.Job[] = [] |
126 | 133 | ||
134 | const filteredJobTypes = this.filterJobTypes(jobType) | ||
135 | |||
127 | // TODO: optimize | 136 | // TODO: optimize |
128 | for (const jobType of jobTypes) { | 137 | for (const jobType of filteredJobTypes) { |
129 | const queue = this.queues[ jobType ] | 138 | const queue = this.queues[ jobType ] |
130 | if (queue === undefined) { | 139 | if (queue === undefined) { |
131 | logger.error('Unknown queue %s to list jobs.', jobType) | 140 | logger.error('Unknown queue %s to list jobs.', jobType) |
@@ -149,10 +158,12 @@ class JobQueue { | |||
149 | return results.slice(start, start + count) | 158 | return results.slice(start, start + count) |
150 | } | 159 | } |
151 | 160 | ||
152 | async count (state: JobState): Promise<number> { | 161 | async count (state: JobState, jobType?: JobType): Promise<number> { |
153 | let total = 0 | 162 | let total = 0 |
154 | 163 | ||
155 | for (const type of jobTypes) { | 164 | const filteredJobTypes = this.filterJobTypes(jobType) |
165 | |||
166 | for (const type of filteredJobTypes) { | ||
156 | const queue = this.queues[ type ] | 167 | const queue = this.queues[ type ] |
157 | if (queue === undefined) { | 168 | if (queue === undefined) { |
158 | logger.error('Unknown queue %s to count jobs.', type) | 169 | logger.error('Unknown queue %s to count jobs.', type) |
@@ -180,6 +191,12 @@ class JobQueue { | |||
180 | }) | 191 | }) |
181 | } | 192 | } |
182 | 193 | ||
194 | private filterJobTypes (jobType?: JobType) { | ||
195 | if (!jobType) return jobTypes | ||
196 | |||
197 | return jobTypes.filter(t => t === jobType) | ||
198 | } | ||
199 | |||
183 | static get Instance () { | 200 | static get Instance () { |
184 | return this.instance || (this.instance = new this()) | 201 | return this.instance || (this.instance = new this()) |
185 | } | 202 | } |
@@ -188,5 +205,6 @@ class JobQueue { | |||
188 | // --------------------------------------------------------------------------- | 205 | // --------------------------------------------------------------------------- |
189 | 206 | ||
190 | export { | 207 | export { |
208 | jobTypes, | ||
191 | JobQueue | 209 | JobQueue |
192 | } | 210 | } |
diff --git a/server/middlewares/validators/jobs.ts b/server/middlewares/validators/jobs.ts index 41a8d6899..b57615dbc 100644 --- a/server/middlewares/validators/jobs.ts +++ b/server/middlewares/validators/jobs.ts | |||
@@ -1,13 +1,17 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator' | 2 | import { param, query } from 'express-validator' |
3 | import { isValidJobState } from '../../helpers/custom-validators/jobs' | 3 | import { isValidJobState, isValidJobType } from '../../helpers/custom-validators/jobs' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | 6 | ||
7 | const listJobsValidator = [ | 7 | const listJobsValidator = [ |
8 | param('state').custom(isValidJobState).not().isEmpty().withMessage('Should have a valid job state'), | 8 | param('state') |
9 | .custom(isValidJobState).not().isEmpty().withMessage('Should have a valid job state'), | ||
10 | query('jobType') | ||
11 | .optional() | ||
12 | .custom(isValidJobType).withMessage('Should have a valid job state'), | ||
9 | 13 | ||
10 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 14 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
11 | logger.debug('Checking listJobsValidator parameters.', { parameters: req.params }) | 15 | logger.debug('Checking listJobsValidator parameters.', { parameters: req.params }) |
12 | 16 | ||
13 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts index c70139514..22e237964 100644 --- a/server/tests/api/check-params/jobs.ts +++ b/server/tests/api/check-params/jobs.ts | |||
@@ -51,6 +51,17 @@ describe('Test jobs API validators', function () { | |||
51 | }) | 51 | }) |
52 | }) | 52 | }) |
53 | 53 | ||
54 | it('Should fail with an incorrect job type', async function () { | ||
55 | await makeGetRequest({ | ||
56 | url: server.url, | ||
57 | token: server.accessToken, | ||
58 | path, | ||
59 | query: { | ||
60 | jobType: 'toto' | ||
61 | } | ||
62 | }) | ||
63 | }) | ||
64 | |||
54 | it('Should fail with a bad start pagination', async function () { | 65 | it('Should fail with a bad start pagination', async function () { |
55 | await checkBadStartPagination(server.url, path, server.accessToken) | 66 | await checkBadStartPagination(server.url, path, server.accessToken) |
56 | }) | 67 | }) |
@@ -79,6 +90,7 @@ describe('Test jobs API validators', function () { | |||
79 | statusCodeExpected: 403 | 90 | statusCodeExpected: 403 |
80 | }) | 91 | }) |
81 | }) | 92 | }) |
93 | |||
82 | }) | 94 | }) |
83 | 95 | ||
84 | after(async function () { | 96 | after(async function () { |
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index a0f505474..7e36067f1 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts | |||
@@ -184,7 +184,14 @@ describe('Test handle downs', function () { | |||
184 | const states: JobState[] = [ 'waiting', 'active' ] | 184 | const states: JobState[] = [ 'waiting', 'active' ] |
185 | 185 | ||
186 | for (const state of states) { | 186 | for (const state of states) { |
187 | const res = await getJobsListPaginationAndSort(servers[ 0 ].url, servers[ 0 ].accessToken, state,0, 50, '-createdAt') | 187 | const res = await getJobsListPaginationAndSort({ |
188 | url: servers[ 0 ].url, | ||
189 | accessToken: servers[ 0 ].accessToken, | ||
190 | state: state, | ||
191 | start: 0, | ||
192 | count: 50, | ||
193 | sort: '-createdAt' | ||
194 | }) | ||
188 | expect(res.body.data).to.have.length(0) | 195 | expect(res.body.data).to.have.length(0) |
189 | } | 196 | } |
190 | }) | 197 | }) |
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts index ceea47a85..58d8c8c10 100644 --- a/server/tests/api/server/jobs.ts +++ b/server/tests/api/server/jobs.ts | |||
@@ -41,20 +41,46 @@ describe('Test jobs', function () { | |||
41 | expect(res.body.data).to.have.length.above(2) | 41 | expect(res.body.data).to.have.length.above(2) |
42 | }) | 42 | }) |
43 | 43 | ||
44 | it('Should list jobs with sort and pagination', async function () { | 44 | it('Should list jobs with sort, pagination and job type', async function () { |
45 | const res = await getJobsListPaginationAndSort(servers[1].url, servers[1].accessToken, 'completed', 1, 2, 'createdAt') | 45 | { |
46 | expect(res.body.total).to.be.above(2) | 46 | const res = await getJobsListPaginationAndSort({ |
47 | expect(res.body.data).to.have.lengthOf(2) | 47 | url: servers[ 1 ].url, |
48 | accessToken: servers[ 1 ].accessToken, | ||
49 | state: 'completed', | ||
50 | start: 1, | ||
51 | count: 2, | ||
52 | sort: 'createdAt' | ||
53 | }) | ||
54 | expect(res.body.total).to.be.above(2) | ||
55 | expect(res.body.data).to.have.lengthOf(2) | ||
56 | |||
57 | let job: Job = res.body.data[ 0 ] | ||
58 | // Skip repeat jobs | ||
59 | if (job.type === 'videos-views') job = res.body.data[ 1 ] | ||
60 | |||
61 | expect(job.state).to.equal('completed') | ||
62 | expect(job.type.startsWith('activitypub-')).to.be.true | ||
63 | expect(dateIsValid(job.createdAt as string)).to.be.true | ||
64 | expect(dateIsValid(job.processedOn as string)).to.be.true | ||
65 | expect(dateIsValid(job.finishedOn as string)).to.be.true | ||
66 | } | ||
48 | 67 | ||
49 | let job = res.body.data[0] | 68 | { |
50 | // Skip repeat jobs | 69 | const res = await getJobsListPaginationAndSort({ |
51 | if (job.type === 'videos-views') job = res.body.data[1] | 70 | url: servers[ 1 ].url, |
71 | accessToken: servers[ 1 ].accessToken, | ||
72 | state: 'completed', | ||
73 | start: 0, | ||
74 | count: 100, | ||
75 | sort: 'createdAt', | ||
76 | jobType: 'activitypub-http-broadcast' | ||
77 | }) | ||
78 | expect(res.body.total).to.be.above(2) | ||
52 | 79 | ||
53 | expect(job.state).to.equal('completed') | 80 | for (const j of res.body.data as Job[]) { |
54 | expect(job.type.startsWith('activitypub-')).to.be.true | 81 | expect(j.type).to.equal('activitypub-http-broadcast') |
55 | expect(dateIsValid(job.createdAt)).to.be.true | 82 | } |
56 | expect(dateIsValid(job.processedOn)).to.be.true | 83 | } |
57 | expect(dateIsValid(job.finishedOn)).to.be.true | ||
58 | }) | 84 | }) |
59 | 85 | ||
60 | after(async function () { | 86 | after(async function () { |
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts index 8b070004d..cba5ac311 100644 --- a/server/tests/real-world/real-world.ts +++ b/server/tests/real-world/real-world.ts | |||
@@ -354,7 +354,14 @@ async function isTherePendingRequests (servers: ServerInfo[]) { | |||
354 | // Check if each server has pending request | 354 | // Check if each server has pending request |
355 | for (const server of servers) { | 355 | for (const server of servers) { |
356 | for (const state of states) { | 356 | for (const state of states) { |
357 | const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt') | 357 | const p = getJobsListPaginationAndSort({ |
358 | url: server.url, | ||
359 | accessToken: server.accessToken, | ||
360 | state: state, | ||
361 | start: 0, | ||
362 | count: 10, | ||
363 | sort: '-createdAt' | ||
364 | }) | ||
358 | .then(res => { | 365 | .then(res => { |
359 | if (res.body.total > 0) pendingRequests = true | 366 | if (res.body.total > 0) pendingRequests = true |
360 | }) | 367 | }) |
diff --git a/shared/extra-utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts index b3db885e8..cc1352e14 100644 --- a/shared/extra-utils/server/jobs.ts +++ b/shared/extra-utils/server/jobs.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { Job, JobState } from '../../models' | 2 | import { Job, JobState, JobType } from '../../models' |
3 | import { wait } from '../miscs/miscs' | 3 | import { wait } from '../miscs/miscs' |
4 | import { ServerInfo } from './servers' | 4 | import { ServerInfo } from './servers' |
5 | import { makeGetRequest } from '@shared/extra-utils' | ||
5 | 6 | ||
6 | function getJobsList (url: string, accessToken: string, state: JobState) { | 7 | function getJobsList (url: string, accessToken: string, state: JobState) { |
7 | const path = '/api/v1/jobs/' + state | 8 | const path = '/api/v1/jobs/' + state |
@@ -14,18 +15,32 @@ function getJobsList (url: string, accessToken: string, state: JobState) { | |||
14 | .expect('Content-Type', /json/) | 15 | .expect('Content-Type', /json/) |
15 | } | 16 | } |
16 | 17 | ||
17 | function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) { | 18 | function getJobsListPaginationAndSort (options: { |
19 | url: string, | ||
20 | accessToken: string, | ||
21 | state: JobState, | ||
22 | start: number, | ||
23 | count: number, | ||
24 | sort: string, | ||
25 | jobType?: JobType | ||
26 | }) { | ||
27 | const { url, accessToken, state, start, count, sort, jobType } = options | ||
18 | const path = '/api/v1/jobs/' + state | 28 | const path = '/api/v1/jobs/' + state |
19 | 29 | ||
20 | return request(url) | 30 | const query = { |
21 | .get(path) | 31 | start, |
22 | .query({ start }) | 32 | count, |
23 | .query({ count }) | 33 | sort, |
24 | .query({ sort }) | 34 | jobType |
25 | .set('Accept', 'application/json') | 35 | } |
26 | .set('Authorization', 'Bearer ' + accessToken) | 36 | |
27 | .expect(200) | 37 | return makeGetRequest({ |
28 | .expect('Content-Type', /json/) | 38 | url, |
39 | path, | ||
40 | token: accessToken, | ||
41 | statusCodeExpected: 200, | ||
42 | query | ||
43 | }) | ||
29 | } | 44 | } |
30 | 45 | ||
31 | async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { | 46 | async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { |
@@ -44,7 +59,14 @@ async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { | |||
44 | // Check if each server has pending request | 59 | // Check if each server has pending request |
45 | for (const server of servers) { | 60 | for (const server of servers) { |
46 | for (const state of states) { | 61 | for (const state of states) { |
47 | const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt') | 62 | const p = getJobsListPaginationAndSort({ |
63 | url: server.url, | ||
64 | accessToken: server.accessToken, | ||
65 | state: state, | ||
66 | start: 0, | ||
67 | count: 10, | ||
68 | sort: '-createdAt' | ||
69 | }) | ||
48 | .then(res => res.body.data) | 70 | .then(res => res.body.data) |
49 | .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views')) | 71 | .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views')) |
50 | .then(jobs => { | 72 | .then(jobs => { |
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts index 1b9aa8a07..b82a633b2 100644 --- a/shared/models/server/job.model.ts +++ b/shared/models/server/job.model.ts | |||
@@ -17,7 +17,7 @@ export interface Job { | |||
17 | type: JobType | 17 | type: JobType |
18 | data: any, | 18 | data: any, |
19 | error: any, | 19 | error: any, |
20 | createdAt: Date | 20 | createdAt: Date | string |
21 | finishedOn: Date | 21 | finishedOn: Date | string |
22 | processedOn: Date | 22 | processedOn: Date | string |
23 | } | 23 | } |