Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
6fd16ebc
Commit
6fd16ebc
authored
Feb 15, 2022
by
Jose Ivan Vargas
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '347856-runner-jobs' into 'master'
Add runner jobs tab See merge request gitlab-org/gitlab!80380
parents
8b3dcdd8
ccd1a1ab
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
683 additions
and
4 deletions
+683
-4
app/assets/javascripts/runner/components/cells/link_cell.vue
app/assets/javascripts/runner/components/cells/link_cell.vue
+27
-0
app/assets/javascripts/runner/components/runner_details.vue
app/assets/javascripts/runner/components/runner_details.vue
+19
-2
app/assets/javascripts/runner/components/runner_jobs.vue
app/assets/javascripts/runner/components/runner_jobs.vue
+82
-0
app/assets/javascripts/runner/components/runner_jobs_table.vue
...ssets/javascripts/runner/components/runner_jobs_table.vue
+95
-0
app/assets/javascripts/runner/constants.js
app/assets/javascripts/runner/constants.js
+2
-0
app/assets/javascripts/runner/graphql/get_runner_jobs.query.graphql
.../javascripts/runner/graphql/get_runner_jobs.query.graphql
+36
-0
app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
...pts/runner/graphql/runner_details_shared.fragment.graphql
+1
-0
locale/gitlab.pot
locale/gitlab.pot
+12
-0
spec/frontend/fixtures/runner.rb
spec/frontend/fixtures/runner.rb
+17
-0
spec/frontend/runner/components/cells/link_cell_spec.js
spec/frontend/runner/components/cells/link_cell_spec.js
+72
-0
spec/frontend/runner/components/runner_details_spec.js
spec/frontend/runner/components/runner_details_spec.js
+41
-1
spec/frontend/runner/components/runner_jobs_spec.js
spec/frontend/runner/components/runner_jobs_spec.js
+156
-0
spec/frontend/runner/components/runner_jobs_table_spec.js
spec/frontend/runner/components/runner_jobs_table_spec.js
+119
-0
spec/frontend/runner/components/runner_update_form_spec.js
spec/frontend/runner/components/runner_update_form_spec.js
+2
-1
spec/frontend/runner/mock_data.js
spec/frontend/runner/mock_data.js
+2
-0
No files found.
app/assets/javascripts/runner/components/cells/link_cell.vue
0 → 100644
View file @
6fd16ebc
<
script
>
import
{
GlLink
}
from
'
@gitlab/ui
'
;
export
default
{
props
:
{
href
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
computed
:
{
component
()
{
if
(
this
.
href
)
{
return
GlLink
;
}
return
'
span
'
;
},
},
};
</
script
>
<
template
>
<component
:is=
"component"
:href=
"href"
v-bind=
"$attrs"
v-on=
"$listeners"
>
<slot></slot>
</component>
</
template
>
app/assets/javascripts/runner/components/runner_details.vue
View file @
6fd16ebc
<
script
>
import
{
GlTabs
,
GlTab
,
GlIntersperse
}
from
'
@gitlab/ui
'
;
import
{
Gl
Badge
,
Gl
Tabs
,
GlTab
,
GlIntersperse
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
timeIntervalInWords
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
ACCESS_LEVEL_REF_PROTECTED
,
GROUP_TYPE
,
PROJECT_TYPE
}
from
'
../constants
'
;
import
{
formatJobCount
}
from
'
../utils
'
;
import
RunnerDetail
from
'
./runner_detail.vue
'
;
import
RunnerGroups
from
'
./runner_groups.vue
'
;
import
RunnerProjects
from
'
./runner_projects.vue
'
;
import
RunnerJobs
from
'
./runner_jobs.vue
'
;
import
RunnerTags
from
'
./runner_tags.vue
'
;
export
default
{
components
:
{
GlBadge
,
GlTabs
,
GlTab
,
GlIntersperse
,
RunnerDetail
,
RunnerGroups
,
RunnerProjects
,
RunnerJobs
,
RunnerTags
,
TimeAgo
,
},
...
...
@@ -53,6 +57,9 @@ export default {
isProjectRunner
()
{
return
this
.
runner
?.
runnerType
===
PROJECT_TYPE
;
},
jobCount
()
{
return
formatJobCount
(
this
.
runner
?.
jobCount
);
},
},
ACCESS_LEVEL_REF_PROTECTED
,
};
...
...
@@ -65,7 +72,7 @@ export default {
<
template
v-if=
"runner"
>
<div
class=
"gl-pt-4"
>
<dl
class=
"gl-mb-0"
>
<dl
class=
"gl-mb-0"
data-testid=
"runner-details-list"
>
<runner-detail
:label=
"s__('Runners|Description')"
:value=
"runner.description"
/>
<runner-detail
:label=
"s__('Runners|Last contact')"
...
...
@@ -103,5 +110,15 @@ export default {
<runner-projects
v-if=
"isProjectRunner"
:runner=
"runner"
/>
</template>
</gl-tab>
<gl-tab>
<
template
#title
>
{{
s__
(
'
Runners|Jobs
'
)
}}
<gl-badge
v-if=
"jobCount"
data-testid=
"job-count-badge"
class=
"gl-ml-1"
size=
"sm"
>
{{
jobCount
}}
</gl-badge>
</
template
>
<runner-jobs
v-if=
"runner"
:runner=
"runner"
/>
</gl-tab>
</gl-tabs>
</template>
app/assets/javascripts/runner/components/runner_jobs.vue
0 → 100644
View file @
6fd16ebc
<
script
>
import
{
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
{
createAlert
}
from
'
~/flash
'
;
import
getRunnerJobsQuery
from
'
../graphql/get_runner_jobs.query.graphql
'
;
import
{
I18N_FETCH_ERROR
,
I18N_NO_JOBS_FOUND
,
RUNNER_DETAILS_JOBS_PAGE_SIZE
}
from
'
../constants
'
;
import
{
captureException
}
from
'
../sentry_utils
'
;
import
{
getPaginationVariables
}
from
'
../utils
'
;
import
RunnerJobsTable
from
'
./runner_jobs_table.vue
'
;
import
RunnerPagination
from
'
./runner_pagination.vue
'
;
export
default
{
name
:
'
RunnerJobs
'
,
components
:
{
GlSkeletonLoading
,
RunnerJobsTable
,
RunnerPagination
,
},
props
:
{
runner
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
jobs
:
{
items
:
[],
pageInfo
:
{},
},
pagination
:
{
page
:
1
,
},
};
},
apollo
:
{
jobs
:
{
query
:
getRunnerJobsQuery
,
variables
()
{
return
this
.
variables
;
},
update
({
runner
})
{
return
{
items
:
runner
?.
jobs
?.
nodes
||
[],
pageInfo
:
runner
?.
jobs
?.
pageInfo
||
{},
};
},
error
(
error
)
{
createAlert
({
message
:
I18N_FETCH_ERROR
});
this
.
reportToSentry
(
error
);
},
},
},
computed
:
{
variables
()
{
const
{
id
}
=
this
.
runner
;
return
{
id
,
...
getPaginationVariables
(
this
.
pagination
,
RUNNER_DETAILS_JOBS_PAGE_SIZE
),
};
},
loading
()
{
return
this
.
$apollo
.
queries
.
jobs
.
loading
;
},
},
methods
:
{
reportToSentry
(
error
)
{
captureException
({
error
,
component
:
this
.
$options
.
name
});
},
},
I18N_NO_JOBS_FOUND
,
};
</
script
>
<
template
>
<div
class=
"gl-pt-3"
>
<gl-skeleton-loading
v-if=
"loading"
class=
"gl-py-5"
/>
<runner-jobs-table
v-else-if=
"jobs.items.length"
:jobs=
"jobs.items"
/>
<p
v-else
>
{{
$options
.
I18N_NO_JOBS_FOUND
}}
</p>
<runner-pagination
v-model=
"pagination"
:disabled=
"loading"
:page-info=
"jobs.pageInfo"
/>
</div>
</
template
>
app/assets/javascripts/runner/components/runner_jobs_table.vue
0 → 100644
View file @
6fd16ebc
<
script
>
import
{
GlTableLite
}
from
'
@gitlab/ui
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
CiBadge
from
'
~/vue_shared/components/ci_badge_link.vue
'
;
import
RunnerTags
from
'
~/runner/components/runner_tags.vue
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
tableField
}
from
'
../utils
'
;
import
LinkCell
from
'
./cells/link_cell.vue
'
;
export
default
{
components
:
{
CiBadge
,
GlTableLite
,
LinkCell
,
RunnerTags
,
TimeAgo
,
},
props
:
{
jobs
:
{
type
:
Array
,
required
:
true
,
},
},
methods
:
{
trAttr
(
job
)
{
if
(
job
?.
id
)
{
return
{
'
data-testid
'
:
`job-row-
${
getIdFromGraphQLId
(
job
.
id
)}
`
};
}
return
{};
},
jobId
(
job
)
{
return
getIdFromGraphQLId
(
job
.
id
);
},
jobPath
(
job
)
{
return
job
.
detailedStatus
?.
detailsPath
;
},
projectName
(
job
)
{
return
job
.
pipeline
?.
project
?.
name
;
},
projectWebUrl
(
job
)
{
return
job
.
pipeline
?.
project
?.
webUrl
;
},
commitShortSha
(
job
)
{
return
job
.
shortSha
;
},
commitPath
(
job
)
{
return
job
.
commitPath
;
},
},
fields
:
[
tableField
({
key
:
'
status
'
,
label
:
s__
(
'
Job|Status
'
)
}),
tableField
({
key
:
'
job
'
,
label
:
__
(
'
Job
'
)
}),
tableField
({
key
:
'
project
'
,
label
:
__
(
'
Project
'
)
}),
tableField
({
key
:
'
commit
'
,
label
:
__
(
'
Commit
'
)
}),
tableField
({
key
:
'
finished_at
'
,
label
:
s__
(
'
Job|Finished at
'
)
}),
tableField
({
key
:
'
tags
'
,
label
:
s__
(
'
Runners|Tags
'
)
}),
],
};
</
script
>
<
template
>
<gl-table-lite
:items=
"jobs"
:fields=
"$options.fields"
:tbody-tr-attr=
"trAttr"
primary-key=
"id"
stacked=
"md"
fixed
>
<template
#cell(status)=
"
{ item = {} }">
<ci-badge
v-if=
"item.detailedStatus"
:status=
"item.detailedStatus"
/>
</
template
>
<
template
#cell(job)=
"{ item = {} }"
>
<link-cell
:href=
"jobPath(item)"
>
#
{{
jobId
(
item
)
}}
</link-cell>
</
template
>
<
template
#cell(project)=
"{ item = {} }"
>
<link-cell
:href=
"projectWebUrl(item)"
>
{{
projectName
(
item
)
}}
</link-cell>
</
template
>
<
template
#cell(commit)=
"{ item = {} }"
>
<link-cell
:href=
"commitPath(item)"
>
{{
commitShortSha
(
item
)
}}
</link-cell>
</
template
>
<
template
#cell(tags)=
"{ item = {} }"
>
<runner-tags
:tag-list=
"item.tags"
/>
</
template
>
<
template
#cell(finished_at)=
"{ item = {} }"
>
<time-ago
v-if=
"item.finishedAt"
:time=
"item.finishedAt"
/>
</
template
>
</gl-table-lite>
</template>
app/assets/javascripts/runner/constants.js
View file @
6fd16ebc
...
...
@@ -4,6 +4,7 @@ export const RUNNER_PAGE_SIZE = 20;
export
const
RUNNER_JOB_COUNT_LIMIT
=
1000
;
export
const
RUNNER_DETAILS_PROJECTS_PAGE_SIZE
=
5
;
export
const
RUNNER_DETAILS_JOBS_PAGE_SIZE
=
30
;
export
const
I18N_FETCH_ERROR
=
s__
(
'
Runners|Something went wrong while fetching runner data.
'
);
export
const
I18N_DETAILS_TITLE
=
s__
(
'
Runners|Runner #%{runner_id}
'
);
...
...
@@ -45,6 +46,7 @@ export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run
export
const
I18N_ASSIGNED_PROJECTS
=
s__
(
'
Runners|Assigned Projects (%{projectCount})
'
);
export
const
I18N_NONE
=
__
(
'
None
'
);
export
const
I18N_NO_JOBS_FOUND
=
s__
(
'
Runner|This runner has not run any jobs.
'
);
// Styles
...
...
app/assets/javascripts/runner/graphql/get_runner_jobs.query.graphql
0 → 100644
View file @
6fd16ebc
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query
getRunnerJobs
(
$id
:
CiRunnerID
!,
$first
:
Int
,
$last
:
Int
,
$before
:
String
,
$after
:
String
)
{
runner
(
id
:
$id
)
{
id
projectCount
jobs
(
before
:
$before
,
after
:
$after
,
first
:
$first
,
last
:
$last
)
{
nodes
{
id
detailedStatus
{
# fields for `<ci-badge>`
id
detailsPath
group
icon
text
}
pipeline
{
id
project
{
id
name
webUrl
}
}
shortSha
commitPath
tags
finishedAt
}
pageInfo
{
...
PageInfo
}
}
}
}
app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
View file @
6fd16ebc
...
...
@@ -8,6 +8,7 @@ fragment RunnerDetailsShared on CiRunner {
ipAddress
description
maximumTimeout
jobCount
tagList
createdAt
status
(
legacyMode
:
null
)
...
...
locale/gitlab.pot
View file @
6fd16ebc
...
...
@@ -20878,6 +20878,9 @@ msgstr ""
msgid "Job|Erase job log and artifacts"
msgstr ""
msgid "Job|Finished at"
msgstr ""
msgid "Job|Job artifacts"
msgstr ""
...
...
@@ -20902,6 +20905,9 @@ msgstr ""
msgid "Job|Show complete raw"
msgstr ""
msgid "Job|Status"
msgstr ""
msgid "Job|The artifacts were removed"
msgstr ""
...
...
@@ -31321,6 +31327,9 @@ msgstr ""
msgid "Runners|Instance"
msgstr ""
msgid "Runners|Jobs"
msgstr ""
msgid "Runners|Last contact"
msgstr ""
...
...
@@ -31561,6 +31570,9 @@ msgstr ""
msgid "Runners|stale"
msgstr ""
msgid "Runner|This runner has not run any jobs."
msgstr ""
msgid "Running"
msgstr ""
...
...
spec/frontend/fixtures/runner.rb
View file @
6fd16ebc
...
...
@@ -17,6 +17,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
let_it_be
(
:group_runner
)
{
create
(
:ci_runner
,
:group
,
groups:
[
group
],
active:
false
,
version:
'2.0.0'
,
revision:
'456'
,
description:
'Group runner'
,
ip_address:
'127.0.0.1'
)
}
let_it_be
(
:group_runner_2
)
{
create
(
:ci_runner
,
:group
,
groups:
[
group
],
active:
false
,
version:
'2.0.0'
,
revision:
'456'
,
description:
'Group runner 2'
,
ip_address:
'127.0.0.1'
)
}
let_it_be
(
:project_runner
)
{
create
(
:ci_runner
,
:project
,
projects:
[
project
,
project_2
],
active:
false
,
version:
'2.0.0'
,
revision:
'456'
,
description:
'Project runner'
,
ip_address:
'127.0.0.1'
)
}
let_it_be
(
:build
)
{
create
(
:ci_build
,
runner:
instance_runner
)
}
query_path
=
'runner/graphql/'
fixtures_path
=
'graphql/runner/'
...
...
@@ -104,6 +105,22 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
expect_graphql_errors_to_be_empty
end
end
describe
GraphQL
::
Query
,
type: :request
do
get_runner_jobs_query_name
=
'get_runner_jobs.query.graphql'
let_it_be
(
:query
)
do
get_graphql_query_as_string
(
"
#{
query_path
}#{
get_runner_jobs_query_name
}
"
)
end
it
"
#{
fixtures_path
}#{
get_runner_jobs_query_name
}
.json"
do
post_graphql
(
query
,
current_user:
admin
,
variables:
{
id:
instance_runner
.
to_global_id
.
to_s
})
expect_graphql_errors_to_be_empty
end
end
end
describe
do
...
...
spec/frontend/runner/components/cells/link_cell_spec.js
0 → 100644
View file @
6fd16ebc
import
{
GlLink
}
from
'
@gitlab/ui
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
LinkCell
from
'
~/runner/components/cells/link_cell.vue
'
;
describe
(
'
LinkCell
'
,
()
=>
{
let
wrapper
;
const
findGlLink
=
()
=>
wrapper
.
find
(
GlLink
);
const
findSpan
=
()
=>
wrapper
.
find
(
'
span
'
);
const
createComponent
=
({
props
=
{},
...
options
}
=
{})
=>
{
wrapper
=
shallowMountExtended
(
LinkCell
,
{
propsData
:
{
...
props
,
},
...
options
,
});
};
it
(
'
when an href is provided, renders a link
'
,
()
=>
{
createComponent
({
props
:
{
href
:
'
/url
'
}
});
expect
(
findGlLink
().
exists
()).
toBe
(
true
);
});
it
(
'
when an href is not provided, renders no link
'
,
()
=>
{
createComponent
();
expect
(
findGlLink
().
exists
()).
toBe
(
false
);
});
describe
.
each
`
href | findContent
${
null
}
|
${
findSpan
}
${
'
/url
'
}
|
${
findGlLink
}
`
(
'
When href is $href
'
,
({
href
,
findContent
})
=>
{
const
content
=
'
My Text
'
;
const
attrs
=
{
foo
:
'
bar
'
};
const
listeners
=
{
click
:
jest
.
fn
(),
};
beforeEach
(()
=>
{
createComponent
({
props
:
{
href
},
slots
:
{
default
:
content
,
},
attrs
,
listeners
,
});
});
afterAll
(()
=>
{
listeners
.
click
.
mockReset
();
});
it
(
'
Renders content
'
,
()
=>
{
expect
(
findContent
().
text
()).
toBe
(
content
);
});
it
(
'
Passes attributes
'
,
()
=>
{
expect
(
findContent
().
attributes
()).
toMatchObject
(
attrs
);
});
it
(
'
Passes event listeners
'
,
()
=>
{
expect
(
listeners
.
click
).
toHaveBeenCalledTimes
(
0
);
findContent
().
vm
.
$emit
(
'
click
'
);
expect
(
listeners
.
click
).
toHaveBeenCalledTimes
(
1
);
});
});
});
spec/frontend/runner/components/runner_details_spec.js
View file @
6fd16ebc
import
{
GlSprintf
,
GlIntersperse
}
from
'
@gitlab/ui
'
;
import
{
GlSprintf
,
GlIntersperse
,
GlTab
}
from
'
@gitlab/ui
'
;
import
{
createWrapper
,
ErrorWrapper
}
from
'
@vue/test-utils
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
...
...
@@ -8,6 +8,7 @@ import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner
import
RunnerDetails
from
'
~/runner/components/runner_details.vue
'
;
import
RunnerDetail
from
'
~/runner/components/runner_detail.vue
'
;
import
RunnerGroups
from
'
~/runner/components/runner_groups.vue
'
;
import
RunnersJobs
from
'
~/runner/components/runner_jobs.vue
'
;
import
RunnerTags
from
'
~/runner/components/runner_tags.vue
'
;
import
RunnerTag
from
'
~/runner/components/runner_tag.vue
'
;
...
...
@@ -38,6 +39,8 @@ describe('RunnerDetails', () => {
};
const
findDetailGroups
=
()
=>
wrapper
.
findComponent
(
RunnerGroups
);
const
findRunnersJobs
=
()
=>
wrapper
.
findComponent
(
RunnersJobs
);
const
findJobCountBadge
=
()
=>
wrapper
.
findByTestId
(
'
job-count-badge
'
);
const
createComponent
=
({
props
=
{},
mountFn
=
shallowMountExtended
,
stubs
}
=
{})
=>
{
wrapper
=
mountFn
(
RunnerDetails
,
{
...
...
@@ -146,4 +149,41 @@ describe('RunnerDetails', () => {
});
});
});
describe
(
'
Jobs tab
'
,
()
=>
{
const
stubs
=
{
GlTab
};
it
(
'
without a runner, shows no jobs
'
,
()
=>
{
createComponent
({
props
:
{
runner
:
null
},
stubs
,
});
expect
(
findJobCountBadge
().
exists
()).
toBe
(
false
);
expect
(
findRunnersJobs
().
exists
()).
toBe
(
false
);
});
it
(
'
without a job count, shows no jobs count
'
,
()
=>
{
createComponent
({
props
:
{
runner
:
{
...
mockRunner
,
jobCount
:
undefined
},
},
stubs
,
});
expect
(
findJobCountBadge
().
exists
()).
toBe
(
false
);
});
it
(
'
with a job count, shows jobs count
'
,
()
=>
{
const
runner
=
{
...
mockRunner
,
jobCount
:
3
};
createComponent
({
props
:
{
runner
},
stubs
,
});
expect
(
findJobCountBadge
().
text
()).
toBe
(
'
3
'
);
expect
(
findRunnersJobs
().
props
(
'
runner
'
)).
toBe
(
runner
);
});
});
});
spec/frontend/runner/components/runner_jobs_spec.js
0 → 100644
View file @
6fd16ebc
import
{
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
createAlert
}
from
'
~/flash
'
;
import
RunnerJobs
from
'
~/runner/components/runner_jobs.vue
'
;
import
RunnerJobsTable
from
'
~/runner/components/runner_jobs_table.vue
'
;
import
RunnerPagination
from
'
~/runner/components/runner_pagination.vue
'
;
import
{
captureException
}
from
'
~/runner/sentry_utils
'
;
import
{
I18N_NO_JOBS_FOUND
,
RUNNER_DETAILS_JOBS_PAGE_SIZE
}
from
'
~/runner/constants
'
;
import
getRunnerJobsQuery
from
'
~/runner/graphql/get_runner_jobs.query.graphql
'
;
import
{
runnerData
,
runnerJobsData
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/runner/sentry_utils
'
);
const
mockRunner
=
runnerData
.
data
.
runner
;
const
mockRunnerWithJobs
=
runnerJobsData
.
data
.
runner
;
const
mockJobs
=
mockRunnerWithJobs
.
jobs
.
nodes
;
Vue
.
use
(
VueApollo
);
describe
(
'
RunnerJobs
'
,
()
=>
{
let
wrapper
;
let
mockRunnerJobsQuery
;
const
findGlSkeletonLoading
=
()
=>
wrapper
.
findComponent
(
GlSkeletonLoading
);
const
findRunnerJobsTable
=
()
=>
wrapper
.
findComponent
(
RunnerJobsTable
);
const
findRunnerPagination
=
()
=>
wrapper
.
findComponent
(
RunnerPagination
);
const
createComponent
=
({
mountFn
=
shallowMountExtended
}
=
{})
=>
{
wrapper
=
mountFn
(
RunnerJobs
,
{
apolloProvider
:
createMockApollo
([[
getRunnerJobsQuery
,
mockRunnerJobsQuery
]]),
propsData
:
{
runner
:
mockRunner
,
},
});
};
beforeEach
(()
=>
{
mockRunnerJobsQuery
=
jest
.
fn
();
});
afterEach
(()
=>
{
mockRunnerJobsQuery
.
mockReset
();
wrapper
.
destroy
();
});
it
(
'
Requests runner jobs
'
,
async
()
=>
{
createComponent
();
await
waitForPromises
();
expect
(
mockRunnerJobsQuery
).
toHaveBeenCalledTimes
(
1
);
expect
(
mockRunnerJobsQuery
).
toHaveBeenCalledWith
({
id
:
mockRunner
.
id
,
first
:
RUNNER_DETAILS_JOBS_PAGE_SIZE
,
});
});
describe
(
'
When there are jobs assigned
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockRunnerJobsQuery
.
mockResolvedValueOnce
(
runnerJobsData
);
createComponent
();
await
waitForPromises
();
});
it
(
'
Shows jobs
'
,
()
=>
{
const
jobs
=
findRunnerJobsTable
().
props
(
'
jobs
'
);
expect
(
jobs
).
toHaveLength
(
mockJobs
.
length
);
expect
(
jobs
[
0
]).
toMatchObject
(
mockJobs
[
0
]);
});
describe
(
'
When "Next" page is clicked
'
,
()
=>
{
beforeEach
(
async
()
=>
{
findRunnerPagination
().
vm
.
$emit
(
'
input
'
,
{
page
:
2
,
after
:
'
AFTER_CURSOR
'
});
await
waitForPromises
();
});
it
(
'
A new page is requested
'
,
()
=>
{
expect
(
mockRunnerJobsQuery
).
toHaveBeenCalledTimes
(
2
);
expect
(
mockRunnerJobsQuery
).
toHaveBeenLastCalledWith
({
id
:
mockRunner
.
id
,
first
:
RUNNER_DETAILS_JOBS_PAGE_SIZE
,
after
:
'
AFTER_CURSOR
'
,
});
});
});
});
describe
(
'
When loading
'
,
()
=>
{
it
(
'
shows loading indicator and no other content
'
,
()
=>
{
createComponent
();
expect
(
findGlSkeletonLoading
().
exists
()).
toBe
(
true
);
expect
(
findRunnerJobsTable
().
exists
()).
toBe
(
false
);
expect
(
findRunnerPagination
().
attributes
(
'
disabled
'
)).
toBe
(
'
true
'
);
});
});
describe
(
'
When there are no jobs
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockRunnerJobsQuery
.
mockResolvedValueOnce
({
data
:
{
runner
:
{
id
:
mockRunner
.
id
,
projectCount
:
0
,
jobs
:
{
nodes
:
[],
pageInfo
:
{
hasNextPage
:
false
,
hasPreviousPage
:
false
,
startCursor
:
''
,
endCursor
:
''
,
},
},
},
},
});
createComponent
();
await
waitForPromises
();
});
it
(
'
Shows a "None" label
'
,
()
=>
{
expect
(
wrapper
.
text
()).
toBe
(
I18N_NO_JOBS_FOUND
);
});
});
describe
(
'
When an error occurs
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockRunnerJobsQuery
.
mockRejectedValue
(
new
Error
(
'
Error!
'
));
createComponent
();
await
waitForPromises
();
});
it
(
'
shows an error
'
,
()
=>
{
expect
(
createAlert
).
toHaveBeenCalled
();
});
it
(
'
reports an error
'
,
()
=>
{
expect
(
captureException
).
toHaveBeenCalledWith
({
component
:
'
RunnerJobs
'
,
error
:
expect
.
any
(
Error
),
});
});
});
});
spec/frontend/runner/components/runner_jobs_table_spec.js
0 → 100644
View file @
6fd16ebc
import
{
GlTableLite
}
from
'
@gitlab/ui
'
;
import
{
extendedWrapper
,
shallowMountExtended
,
mountExtended
,
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
RunnerJobsTable
from
'
~/runner/components/runner_jobs_table.vue
'
;
import
{
useFakeDate
}
from
'
helpers/fake_date
'
;
import
{
runnerJobsData
}
from
'
../mock_data
'
;
const
mockJobs
=
runnerJobsData
.
data
.
runner
.
jobs
.
nodes
;
describe
(
'
RunnerJobsTable
'
,
()
=>
{
let
wrapper
;
const
mockNow
=
'
2021-01-15T12:00:00Z
'
;
const
mockOneHourAgo
=
'
2021-01-15T11:00:00Z
'
;
useFakeDate
(
mockNow
);
const
findTable
=
()
=>
wrapper
.
findComponent
(
GlTableLite
);
const
findHeaders
=
()
=>
wrapper
.
findAll
(
'
th
'
);
const
findRows
=
()
=>
wrapper
.
findAll
(
'
[data-testid^="job-row-"]
'
);
const
findCell
=
({
field
})
=>
extendedWrapper
(
findRows
().
at
(
0
).
find
(
`[data-testid="td-
${
field
}
"]`
));
const
createComponent
=
({
props
=
{}
}
=
{},
mountFn
=
shallowMountExtended
)
=>
{
wrapper
=
mountFn
(
RunnerJobsTable
,
{
propsData
:
{
jobs
:
mockJobs
,
...
props
,
},
stubs
:
{
GlTableLite
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
Sets job id as a row key
'
,
()
=>
{
createComponent
();
expect
(
findTable
().
attributes
(
'
primarykey
'
)).
toBe
(
'
id
'
);
});
describe
(
'
Table data
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({},
mountExtended
);
});
it
(
'
Displays headers
'
,
()
=>
{
const
headerLabels
=
findHeaders
().
wrappers
.
map
((
w
)
=>
w
.
text
());
expect
(
headerLabels
).
toEqual
([
s__
(
'
Job|Status
'
),
__
(
'
Job
'
),
__
(
'
Project
'
),
__
(
'
Commit
'
),
s__
(
'
Job|Finished at
'
),
s__
(
'
Runners|Tags
'
),
]);
});
it
(
'
Displays a list of jobs
'
,
()
=>
{
expect
(
findRows
()).
toHaveLength
(
1
);
});
it
(
'
Displays details of a job
'
,
()
=>
{
const
{
id
,
detailedStatus
,
pipeline
,
shortSha
,
commitPath
}
=
mockJobs
[
0
];
expect
(
findCell
({
field
:
'
status
'
}).
text
()).
toMatchInterpolatedText
(
detailedStatus
.
text
);
expect
(
findCell
({
field
:
'
job
'
}).
text
()).
toContain
(
`#
${
getIdFromGraphQLId
(
id
)}
`
);
expect
(
findCell
({
field
:
'
job
'
}).
find
(
'
a
'
).
attributes
(
'
href
'
)).
toBe
(
detailedStatus
.
detailsPath
,
);
expect
(
findCell
({
field
:
'
project
'
}).
text
()).
toBe
(
pipeline
.
project
.
name
);
expect
(
findCell
({
field
:
'
project
'
}).
find
(
'
a
'
).
attributes
(
'
href
'
)).
toBe
(
pipeline
.
project
.
webUrl
,
);
expect
(
findCell
({
field
:
'
commit
'
}).
text
()).
toBe
(
shortSha
);
expect
(
findCell
({
field
:
'
commit
'
}).
find
(
'
a
'
).
attributes
(
'
href
'
)).
toBe
(
commitPath
);
});
});
describe
(
'
Table data formatting
'
,
()
=>
{
let
mockJobsCopy
;
beforeEach
(()
=>
{
mockJobsCopy
=
[
{
...
mockJobs
[
0
],
},
];
});
it
(
'
Formats finishedAt time
'
,
()
=>
{
mockJobsCopy
[
0
].
finishedAt
=
mockOneHourAgo
;
createComponent
({
props
:
{
jobs
:
mockJobsCopy
}
},
mountExtended
);
expect
(
findCell
({
field
:
'
finished_at
'
}).
text
()).
toBe
(
'
1 hour ago
'
);
});
it
(
'
Formats tags
'
,
()
=>
{
mockJobsCopy
[
0
].
tags
=
[
'
tag-1
'
,
'
tag-2
'
];
createComponent
({
props
:
{
jobs
:
mockJobsCopy
}
},
mountExtended
);
expect
(
findCell
({
field
:
'
tags
'
}).
text
()).
toMatchInterpolatedText
(
'
tag-1 tag-2
'
);
});
});
});
spec/frontend/runner/components/runner_update_form_spec.js
View file @
6fd16ebc
...
...
@@ -123,6 +123,7 @@ describe('RunnerUpdateForm', () => {
// Some read-only fields are not submitted
const
{
__typename
,
ipAddress
,
runnerType
,
createdAt
,
...
...
@@ -132,7 +133,7 @@ describe('RunnerUpdateForm', () => {
userPermissions
,
version
,
groups
,
__typename
,
jobCount
,
...
submitted
}
=
mockRunner
;
...
...
spec/frontend/runner/mock_data.js
View file @
6fd16ebc
...
...
@@ -7,6 +7,7 @@ import runnersDataPaginated from 'test_fixtures/graphql/runner/get_runners.query
import
runnerData
from
'
test_fixtures/graphql/runner/get_runner.query.graphql.json
'
;
import
runnerWithGroupData
from
'
test_fixtures/graphql/runner/get_runner.query.graphql.with_group.json
'
;
import
runnerProjectsData
from
'
test_fixtures/graphql/runner/get_runner_projects.query.graphql.json
'
;
import
runnerJobsData
from
'
test_fixtures/graphql/runner/get_runner_jobs.query.graphql.json
'
;
// Group queries
import
groupRunnersData
from
'
test_fixtures/graphql/runner/get_group_runners.query.graphql.json
'
;
...
...
@@ -20,6 +21,7 @@ export {
runnerData
,
runnerWithGroupData
,
runnerProjectsData
,
runnerJobsData
,
groupRunnersData
,
groupRunnersCountData
,
groupRunnersDataPaginated
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment