2018-05-25 16:13:18 +00:00
import EmberObject from '@ember/object' ;
2018-02-17 02:59:40 +00:00
import { run } from '@ember/runloop' ;
import { assign } from '@ember/polyfills' ;
2019-03-13 00:04:16 +00:00
import { settled } from '@ember/test-helpers' ;
2019-03-14 02:16:39 +00:00
import { setupTest } from 'ember-qunit' ;
import { module , test } from 'qunit' ;
import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage' ;
module ( 'Unit | Adapter | Job' , function ( hooks ) {
setupTest ( hooks ) ;
2019-03-25 22:54:48 +00:00
hooks . beforeEach ( async function ( ) {
2019-03-14 02:16:39 +00:00
this . store = this . owner . lookup ( 'service:store' ) ;
this . subject = ( ) => this . store . adapterFor ( 'job' ) ;
2017-09-19 14:47:10 +00:00
window . sessionStorage . clear ( ) ;
2018-07-05 19:29:29 +00:00
window . localStorage . clear ( ) ;
2017-09-19 14:47:10 +00:00
this . server = startMirage ( ) ;
2018-07-05 20:49:27 +00:00
this . server . create ( 'namespace' ) ;
this . server . create ( 'namespace' , { id : 'some-namespace' } ) ;
2017-09-19 14:47:10 +00:00
this . server . create ( 'node' ) ;
2018-07-05 20:49:27 +00:00
this . server . create ( 'job' , { id : 'job-1' , namespaceId : 'default' } ) ;
2017-10-23 17:22:58 +00:00
this . server . create ( 'job' , { id : 'job-2' , namespaceId : 'some-namespace' } ) ;
2018-07-05 20:49:27 +00:00
2018-08-10 03:27:06 +00:00
this . server . create ( 'region' , { id : 'region-1' } ) ;
this . server . create ( 'region' , { id : 'region-2' } ) ;
2019-03-14 02:16:39 +00:00
this . system = this . owner . lookup ( 'service:system' ) ;
2018-08-09 22:35:25 +00:00
// Namespace, default region, and all regions are requests that all
// job requests depend on. Fetching them ahead of time means testing
// job adapter behavior in isolation.
2019-03-25 22:54:48 +00:00
await this . system . get ( 'namespaces' ) ;
2018-08-09 22:35:25 +00:00
this . system . get ( 'shouldIncludeRegion' ) ;
2019-03-25 22:54:48 +00:00
await this . system . get ( 'defaultRegion' ) ;
2018-07-05 20:49:27 +00:00
// Reset the handledRequests array to avoid accounting for this
// namespaces request everywhere.
this . server . pretender . handledRequests . length = 0 ;
2019-03-14 02:16:39 +00:00
} ) ;
hooks . afterEach ( function ( ) {
2017-09-19 14:47:10 +00:00
this . server . shutdown ( ) ;
2019-03-14 02:16:39 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
test ( 'The job endpoint is the only required endpoint for fetching a job' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ` ] ,
'The only request made is /job/:id'
) ;
2018-08-07 01:33:57 +00:00
} ) ;
2018-07-05 20:49:27 +00:00
2019-03-14 18:35:46 +00:00
test ( 'When a namespace is set in localStorage but a job in the default namespace is requested, the namespace query param is not present' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
window . localStorage . nomadActiveNamespace = 'some-namespace' ;
2018-07-05 20:49:27 +00:00
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2018-07-05 20:49:27 +00:00
2019-03-14 02:16:39 +00:00
this . system . get ( 'namespaces' ) ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ` ] ,
'The only request made is /job/:id with no namespace query param'
) ;
2018-07-05 20:49:27 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'When a namespace is in localStorage and the requested job is in the default namespace, the namespace query param is left out' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
window . localStorage . nomadActiveNamespace = 'red-herring' ;
2018-07-05 19:29:29 +00:00
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2018-07-05 19:29:29 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
2018-07-05 19:29:29 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ` ] ,
'The request made is /job/:id with no namespace query param'
) ;
2018-08-07 01:33:57 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
test ( 'When the job has a namespace other than default, it is in the URL' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-2' ;
const jobNamespace = 'some-namespace' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2017-10-23 17:22:58 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
2017-10-23 17:22:58 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ?namespace= ${ jobNamespace } ` ] ,
'The only request made is /job/:id?namespace=:namespace'
) ;
2018-08-07 01:33:57 +00:00
} ) ;
2017-10-23 17:22:58 +00:00
2019-03-14 18:35:46 +00:00
test ( 'When there is no token set in the token service, no x-nomad-token header is set' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
assert . notOk (
pretender . handledRequests . mapBy ( 'requestHeaders' ) . some ( headers => headers [ 'X-Nomad-Token' ] ) ,
'No token header present on either job request'
) ;
2018-08-07 01:33:57 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 18:35:46 +00:00
test ( 'When a token is set in the token service, then x-nomad-token header is set' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
const secret = 'here is the secret' ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . set ( 'token.secret' , secret ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
2019-03-14 02:16:39 +00:00
2019-03-14 18:35:46 +00:00
assert . ok (
pretender . handledRequests
. mapBy ( 'requestHeaders' )
. every ( headers => headers [ 'X-Nomad-Token' ] === secret ) ,
'The token header is present on both job requests'
) ;
2018-08-07 01:33:57 +00:00
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
test ( 'findAll can be watched' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
const request = ( ) =>
this . subject ( ) . findAll ( null , { modelName : 'job' } , null , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-02-17 02:59:40 +00:00
request ( ) ;
assert . equal (
2019-03-14 02:16:39 +00:00
pretender . handledRequests [ 0 ] . url ,
'/v1/jobs?index=1' ,
'Second request is a blocking request for jobs'
2018-02-17 02:59:40 +00:00
) ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
request ( ) ;
assert . equal (
pretender . handledRequests [ 1 ] . url ,
'/v1/jobs?index=2' ,
'Third request is a blocking request with an incremented index param'
) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
2019-03-14 02:16:39 +00:00
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
test ( 'findRecord can be watched' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
const { pretender } = this . server ;
const request = ( ) =>
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-02-17 02:59:40 +00:00
request ( ) ;
assert . equal (
2019-03-14 02:16:39 +00:00
pretender . handledRequests [ 0 ] . url ,
'/v1/job/job-1?index=1' ,
'Second request is a blocking request for job-1'
2018-02-17 02:59:40 +00:00
) ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
request ( ) ;
assert . equal (
pretender . handledRequests [ 1 ] . url ,
'/v1/job/job-1?index=2' ,
'Third request is a blocking request with an incremented index param'
) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
2018-07-05 20:49:27 +00:00
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
test ( 'relationships can be reloaded' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const plainId = 'job-1' ;
const mockModel = makeMockModel ( plainId ) ;
this . subject ( ) . reloadRelationship ( mockModel , 'summary' ) ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . equal (
pretender . handledRequests [ 0 ] . url ,
` /v1/job/ ${ plainId } /summary ` ,
'Relationship was reloaded'
) ;
2019-03-14 02:16:39 +00:00
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
test ( 'relationship reloads can be watched' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const plainId = 'job-1' ;
const mockModel = makeMockModel ( plainId ) ;
2018-02-17 02:59:40 +00:00
this . subject ( ) . reloadRelationship ( mockModel , 'summary' , true ) ;
assert . equal (
2019-03-14 02:16:39 +00:00
pretender . handledRequests [ 0 ] . url ,
'/v1/job/job-1/summary?index=1' ,
'First request is a blocking request for job-1 summary relationship'
2018-02-17 02:59:40 +00:00
) ;
2019-03-14 02:16:39 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . reloadRelationship ( mockModel , 'summary' , true ) ;
assert . equal (
pretender . handledRequests [ 1 ] . url ,
'/v1/job/job-1/summary?index=2' ,
'Second request is a blocking request with an incremented index param'
) ;
2018-02-17 02:59:40 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'findAll can be canceled' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
pretender . get ( '/v1/jobs' , ( ) => [ 200 , { } , '[]' ] , true ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( )
. findAll ( null , { modelName : 'job' } , null , {
reload : true ,
adapterOptions : { watch : true } ,
} )
. catch ( ( ) => { } ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
const { request : xhr } = pretender . requestReferences [ 0 ] ;
assert . equal ( xhr . status , 0 , 'Request is still pending' ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
// Schedule the cancelation before waiting
run . next ( ( ) => {
this . subject ( ) . cancelFindAll ( 'job' ) ;
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . ok ( xhr . aborted , 'Request was aborted' ) ;
2018-02-17 02:59:40 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'findRecord can be canceled' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
pretender . get ( '/v1/job/:id' , ( ) => [ 200 , { } , '{}' ] , true ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
const { request : xhr } = pretender . requestReferences [ 0 ] ;
assert . equal ( xhr . status , 0 , 'Request is still pending' ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
// Schedule the cancelation before waiting
run . next ( ( ) => {
this . subject ( ) . cancelFindRecord ( 'job' , jobId ) ;
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . ok ( xhr . aborted , 'Request was aborted' ) ;
2018-02-17 02:59:40 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'relationship reloads can be canceled' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const plainId = 'job-1' ;
const mockModel = makeMockModel ( plainId ) ;
pretender . get ( '/v1/job/:id/summary' , ( ) => [ 200 , { } , '{}' ] , true ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . reloadRelationship ( mockModel , 'summary' , true ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
const { request : xhr } = pretender . requestReferences [ 0 ] ;
assert . equal ( xhr . status , 0 , 'Request is still pending' ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 02:16:39 +00:00
// Schedule the cancelation before waiting
run . next ( ( ) => {
this . subject ( ) . cancelReloadRelationship ( mockModel , 'summary' ) ;
} ) ;
2018-02-17 02:59:40 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . ok ( xhr . aborted , 'Request was aborted' ) ;
2018-02-17 02:59:40 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'requests can be canceled even if multiple requests for the same URL were made' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
2018-03-29 18:44:26 +00:00
2019-03-14 02:16:39 +00:00
pretender . get ( '/v1/job/:id' , ( ) => [ 200 , { } , '{}' ] , true ) ;
2018-03-29 18:44:26 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-03-29 18:44:26 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-03-29 18:44:26 +00:00
2019-03-14 02:16:39 +00:00
const { request : xhr } = pretender . requestReferences [ 0 ] ;
assert . equal ( xhr . status , 0 , 'Request is still pending' ) ;
assert . equal ( pretender . requestReferences . length , 2 , 'Two findRecord requests were made' ) ;
assert . equal (
pretender . requestReferences . mapBy ( 'url' ) . uniq ( ) . length ,
1 ,
'The two requests have the same URL'
) ;
2018-03-29 18:44:26 +00:00
2019-03-14 02:16:39 +00:00
// Schedule the cancelation before waiting
run . next ( ( ) => {
this . subject ( ) . cancelFindRecord ( 'job' , jobId ) ;
} ) ;
2018-03-29 18:44:26 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . ok ( xhr . aborted , 'Request was aborted' ) ;
2018-03-29 18:44:26 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'canceling a find record request will never cancel a request with the same url but different method' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobId = JSON . stringify ( [ 'job-1' , 'default' ] ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 02:16:39 +00:00
pretender . get ( '/v1/job/:id' , ( ) => [ 200 , { } , '{}' ] , true ) ;
pretender . delete ( '/v1/job/:id' , ( ) => [ 204 , { } , '' ] , 200 ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId , {
reload : true ,
adapterOptions : { watch : true } ,
} ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 02:16:39 +00:00
this . subject ( ) . stop ( EmberObject . create ( { id : jobId } ) ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 02:16:39 +00:00
const { request : getXHR } = pretender . requestReferences [ 0 ] ;
const { request : deleteXHR } = pretender . requestReferences [ 1 ] ;
assert . equal ( getXHR . status , 0 , 'Get request is still pending' ) ;
assert . equal ( deleteXHR . status , 0 , 'Delete request is still pending' ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 02:16:39 +00:00
// Schedule the cancelation before waiting
run . next ( ( ) => {
this . subject ( ) . cancelFindRecord ( 'job' , jobId ) ;
} ) ;
2018-05-25 16:13:18 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
assert . ok ( getXHR . aborted , 'Get request was aborted' ) ;
assert . notOk ( deleteXHR . aborted , 'Delete request was aborted' ) ;
2018-05-25 16:13:18 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'when there is no region set, requests are made without the region query param' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
this . subject ( ) . findAll ( null , { modelName : 'job' } , null ) ;
2019-03-14 02:16:39 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ` , '/v1/jobs' ] ,
'No requests include the region query param'
) ;
2018-08-10 03:27:06 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'when there is a region set, requests are made with the region query param' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
const region = 'region-2' ;
window . localStorage . nomadActiveRegion = region ;
2018-08-10 03:27:06 +00:00
2019-03-25 22:54:48 +00:00
// Regions are fetched in the before hook, so manually dirty the activeRegion
// instead of repeating what is in the beforeEach here.
this . system . notifyPropertyChange ( 'activeRegion' ) ;
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2018-08-10 03:27:06 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
this . subject ( ) . findAll ( null , { modelName : 'job' } , null ) ;
2018-08-10 03:27:06 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ?region= ${ region } ` , ` /v1/jobs?region= ${ region } ` ] ,
'Requests include the region query param'
) ;
2018-08-10 03:27:06 +00:00
} ) ;
2019-03-14 18:35:46 +00:00
test ( 'when the region is set to the default region, requests are made without the region query param' , async function ( assert ) {
2019-03-14 02:16:39 +00:00
window . localStorage . nomadActiveRegion = 'region-1' ;
2018-08-10 03:27:06 +00:00
2019-03-25 22:54:48 +00:00
// Regions are fetched in the before hook, so manually dirty the activeRegion
// instead of repeating what is in the beforeEach here.
this . system . notifyPropertyChange ( 'activeRegion' ) ;
2019-03-14 02:16:39 +00:00
const { pretender } = this . server ;
const jobName = 'job-1' ;
const jobNamespace = 'default' ;
const jobId = JSON . stringify ( [ jobName , jobNamespace ] ) ;
2018-08-10 03:27:06 +00:00
2019-03-14 18:35:46 +00:00
await settled ( ) ;
this . subject ( ) . findRecord ( null , { modelName : 'job' } , jobId ) ;
this . subject ( ) . findAll ( null , { modelName : 'job' } , null ) ;
2018-08-10 03:27:06 +00:00
2019-03-14 18:35:46 +00:00
assert . deepEqual (
pretender . handledRequests . mapBy ( 'url' ) ,
[ ` /v1/job/ ${ jobName } ` , '/v1/jobs' ] ,
'No requests include the region query param'
) ;
2018-08-10 03:27:06 +00:00
} ) ;
} ) ;
2018-02-17 02:59:40 +00:00
function makeMockModel ( id , options ) {
return assign (
{
relationshipFor ( name ) {
return {
kind : 'belongsTo' ,
type : 'job-summary' ,
key : name ,
} ;
} ,
belongsTo ( name ) {
return {
link ( ) {
return ` /v1/job/ ${ id } / ${ name } ` ;
} ,
} ;
} ,
} ,
options
) ;
}