Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
e69732e2
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e69732e2
编写于
5月 04, 2017
作者:
F
Filipa Lacerda
提交者:
Phil Hughes
5月 04, 2017
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Pipeline table mini graph dropdown remains open when table is refreshed
上级
dcdced81
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
309 addition
and
99 deletion
+309
-99
app/assets/javascripts/commit/pipelines/pipelines_table.js
app/assets/javascripts/commit/pipelines/pipelines_table.js
+10
-1
app/assets/javascripts/pipelines/components/stage.vue
app/assets/javascripts/pipelines/components/stage.vue
+173
-0
app/assets/javascripts/pipelines/pipelines.js
app/assets/javascripts/pipelines/pipelines.js
+10
-1
app/assets/javascripts/vue_shared/components/pipelines_table.js
...sets/javascripts/vue_shared/components/pipelines_table.js
+9
-2
app/assets/javascripts/vue_shared/components/pipelines_table_row.js
.../javascripts/vue_shared/components/pipelines_table_row.js
+11
-2
app/assets/stylesheets/pages/pipelines.scss
app/assets/stylesheets/pages/pipelines.scss
+22
-28
app/views/shared/_mini_pipeline_graph.html.haml
app/views/shared/_mini_pipeline_graph.html.haml
+4
-4
changelogs/unreleased/31558-job-dropdown.yml
changelogs/unreleased/31558-job-dropdown.yml
+4
-0
spec/javascripts/fixtures/mini_dropdown_graph.html.haml
spec/javascripts/fixtures/mini_dropdown_graph.html.haml
+3
-3
spec/javascripts/pipelines/stage_spec.js
spec/javascripts/pipelines/stage_spec.js
+63
-58
未找到文件。
app/assets/javascripts/commit/pipelines/pipelines_table.js
浏览文件 @
e69732e2
...
...
@@ -46,6 +46,7 @@ export default Vue.component('pipelines-table', {
isLoading
:
false
,
hasError
:
false
,
isMakingRequest
:
false
,
updateGraphDropdown
:
false
,
};
},
...
...
@@ -130,15 +131,21 @@ export default Vue.component('pipelines-table', {
const
pipelines
=
response
.
pipelines
||
response
;
this
.
store
.
storePipelines
(
pipelines
);
this
.
isLoading
=
false
;
this
.
updateGraphDropdown
=
true
;
},
errorCallback
()
{
this
.
hasError
=
true
;
this
.
isLoading
=
false
;
this
.
updateGraphDropdown
=
false
;
},
setIsMakingRequest
(
isMakingRequest
)
{
this
.
isMakingRequest
=
isMakingRequest
;
if
(
isMakingRequest
)
{
this
.
updateGraphDropdown
=
false
;
}
},
},
...
...
@@ -163,7 +170,9 @@ export default Vue.component('pipelines-table', {
v-if="shouldRenderTable">
<pipelines-table-component
:pipelines="state.pipelines"
:service="service" />
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
</div>
`
,
...
...
app/assets/javascripts/pipelines/components/stage.
js
→
app/assets/javascripts/pipelines/components/stage.
vue
浏览文件 @
e69732e2
<
script
>
/**
* Renders each stage of the pipeline mini graph.
*
* Given the provided endpoint will make a request to
* fetch the dropdown data when the stage is clicked.
*
* Request is made inside this component to make it reusable between:
* 1. Pipelines main table
* 2. Pipelines table in commit and Merge request views
* 3. Merge request widget
* 4. Commit widget
*/
/* global Flash */
import
StatusIconEntityMap
from
'
../../ci_status_icons
'
;
...
...
@@ -7,36 +22,55 @@ export default {
type
:
Object
,
required
:
true
,
},
updateDropdown
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
builds
:
''
,
spinner
:
'
<span class="fa fa-spinner fa-spin"></span>
'
,
isLoading
:
false
,
dropdownContent
:
''
,
endpoint
:
this
.
stage
.
dropdown_path
,
};
},
updated
()
{
if
(
this
.
builds
)
{
if
(
this
.
dropdownContent
.
length
>
0
)
{
this
.
stopDropdownClickPropagation
();
}
},
methods
:
{
fetchBuilds
(
e
)
{
const
ariaExpanded
=
e
.
currentTarget
.
attributes
[
'
aria-expanded
'
];
watch
:
{
updateDropdown
()
{
if
(
this
.
updateDropdown
&&
this
.
isDropdownOpen
()
&&
!
this
.
isLoading
)
{
this
.
fetchJobs
();
}
},
},
if
(
ariaExpanded
&&
(
ariaExpanded
.
textContent
===
'
true
'
))
return
null
;
methods
:
{
onClickStage
()
{
if
(
!
this
.
isDropdownOpen
())
{
this
.
isLoading
=
true
;
this
.
fetchJobs
();
}
},
return
this
.
$http
.
get
(
this
.
stage
.
dropdown_path
)
fetchJobs
()
{
this
.
$http
.
get
(
this
.
endpoint
)
.
then
((
response
)
=>
{
this
.
builds
=
JSON
.
parse
(
response
.
body
).
html
;
this
.
dropdownContent
=
response
.
json
().
html
;
this
.
isLoading
=
false
;
})
.
catch
(()
=>
{
// If dropdown is opened we'll close it.
if
(
this
.
$el
.
classList
.
contains
(
'
open
'
))
{
$
(
this
.
$refs
.
dropdown
).
dropdown
(
'
toggle
'
);
}
this
.
closeDropdown
();
this
.
isLoading
=
false
;
const
flash
=
new
Flash
(
'
Something went wrong on our end.
'
);
return
flash
;
...
...
@@ -57,59 +91,83 @@ export default {
e
.
stopPropagation
();
});
},
closeDropdown
()
{
if
(
this
.
isDropdownOpen
())
{
$
(
this
.
$refs
.
dropdown
).
dropdown
(
'
toggle
'
);
}
},
isDropdownOpen
()
{
return
this
.
$el
.
classList
.
contains
(
'
open
'
);
},
},
computed
:
{
buildsOrSpinner
()
{
return
this
.
builds
?
this
.
builds
:
this
.
spinner
;
},
dropdownClass
()
{
if
(
this
.
builds
)
return
'
js-builds-dropdown-container
'
;
return
'
js-builds-dropdown-loading builds-dropdown-loading
'
;
},
buildStatus
()
{
return
`Build:
${
this
.
stage
.
status
.
label
}
`
;
},
tooltip
()
{
return
`has-tooltip ci-status-icon ci-status-icon-
${
this
.
stage
.
status
.
group
}
`
;
return
this
.
dropdownContent
.
length
>
0
?
'
js-builds-dropdown-container
'
:
'
js-builds-dropdown-loading
'
;
},
triggerButtonClass
()
{
return
`
mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button
ci-status-icon-
${
this
.
stage
.
status
.
group
}
`
;
return
`ci-status-icon-
${
this
.
stage
.
status
.
group
}
`
;
},
svgHTML
()
{
svgIcon
()
{
return
StatusIconEntityMap
[
this
.
stage
.
status
.
icon
];
},
},
template
:
`
<div>
<button
@click="fetchBuilds($event)"
:class="triggerButtonClass"
:title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label="stage.title"
ref="dropdown">
<span
v-html="svgHTML"
aria-hidden="true">
</span>
<i
class="fa fa-caret-down"
aria-hidden="true" />
</button>
<ul
ref="dropdown-content"
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div
class="arrow-up"
aria-hidden="true"></div>
};
</
script
>
<
template
>
<div
class=
"dropdown"
>
<button
:class=
"triggerButtonClass"
@
click=
"onClickStage"
class=
"mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button"
:title=
"stage.title"
data-placement=
"top"
data-toggle=
"dropdown"
type=
"button"
id=
"stageDropdown"
aria-haspopup=
"true"
aria-expanded=
"false"
>
<span
v-html=
"svgIcon"
aria-hidden=
"true"
:aria-label=
"stage.title"
>
</span>
<i
class=
"fa fa-caret-down"
aria-hidden=
"true"
>
</i>
</button>
<ul
class=
"dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby=
"stageDropdown"
>
<li
:class=
"dropdownClass"
class=
"js-builds-dropdown-list scrollable-menu"
>
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner">
class=
"text-center"
v-if=
"isLoading"
>
<i
class=
"fa fa-spin fa-spinner"
aria-hidden=
"true"
aria-label=
"Loading"
>
</i>
</div>
</ul>
</div>
`
,
};
<ul
v-else
v-html=
"dropdownContent"
>
</ul>
</li>
</ul>
</div>
</script>
app/assets/javascripts/pipelines/pipelines.js
浏览文件 @
e69732e2
...
...
@@ -49,6 +49,7 @@ export default {
isLoading
:
false
,
hasError
:
false
,
isMakingRequest
:
false
,
updateGraphDropdown
:
false
,
};
},
...
...
@@ -198,15 +199,21 @@ export default {
this
.
store
.
storePagination
(
response
.
headers
);
this
.
isLoading
=
false
;
this
.
updateGraphDropdown
=
true
;
},
errorCallback
()
{
this
.
hasError
=
true
;
this
.
isLoading
=
false
;
this
.
updateGraphDropdown
=
false
;
},
setIsMakingRequest
(
isMakingRequest
)
{
this
.
isMakingRequest
=
isMakingRequest
;
if
(
isMakingRequest
)
{
this
.
updateGraphDropdown
=
false
;
}
},
},
...
...
@@ -263,7 +270,9 @@ export default {
<pipelines-table-component
:pipelines="state.pipelines"
:service="service"/>
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
<gl-pagination
...
...
app/assets/javascripts/vue_shared/components/pipelines_table.js
浏览文件 @
e69732e2
...
...
@@ -10,13 +10,18 @@ export default {
pipelines
:
{
type
:
Array
,
required
:
true
,
default
:
()
=>
([]),
},
service
:
{
type
:
Object
,
required
:
true
,
},
updateGraphDropdown
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
components
:
{
...
...
@@ -40,7 +45,9 @@ export default {
v-bind:model="model">
<tr is="pipelines-table-row-component"
:pipeline="model"
:service="service"></tr>
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</template>
</tbody>
</table>
...
...
app/assets/javascripts/vue_shared/components/pipelines_table_row.js
浏览文件 @
e69732e2
...
...
@@ -3,7 +3,7 @@ import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import
PipelinesActionsComponent
from
'
../../pipelines/components/pipelines_actions
'
;
import
PipelinesArtifactsComponent
from
'
../../pipelines/components/pipelines_artifacts
'
;
import
PipelinesStatusComponent
from
'
../../pipelines/components/status
'
;
import
PipelinesStageComponent
from
'
../../pipelines/components/stage
'
;
import
PipelinesStageComponent
from
'
../../pipelines/components/stage
.vue
'
;
import
PipelinesUrlComponent
from
'
../../pipelines/components/pipeline_url
'
;
import
PipelinesTimeagoComponent
from
'
../../pipelines/components/time_ago
'
;
import
CommitComponent
from
'
./commit
'
;
...
...
@@ -24,6 +24,12 @@ export default {
type
:
Object
,
required
:
true
,
},
updateGraphDropdown
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
components
:
{
...
...
@@ -213,7 +219,10 @@ export default {
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<dropdown-stage :stage="stage"/>
<dropdown-stage
:stage="stage"
:update-dropdown="updateGraphDropdown"/>
</div>
</td>
...
...
app/assets/stylesheets/pages/pipelines.scss
浏览文件 @
e69732e2
...
...
@@ -781,16 +781,11 @@
}
.scrollable-menu
{
padding
:
0
;
max-height
:
245px
;
overflow
:
auto
;
}
// Loading icon
.builds-dropdown-loading
{
margin
:
0
auto
;
width
:
20px
;
}
// Action icon on the right
a
.ci-action-icon-wrapper
{
color
:
$action-icon-color
;
...
...
@@ -893,30 +888,29 @@
* Top arrow in the dropdown in the mini pipeline graph
*/
.mini-pipeline-graph-dropdown-menu
{
.arrow-up
{
&
::before
,
&
::after
{
content
:
''
;
display
:
inline-block
;
position
:
absolute
;
width
:
0
;
height
:
0
;
border-color
:
transparent
;
border-style
:
solid
;
top
:
-6px
;
left
:
2px
;
border-width
:
0
5px
6px
;
}
&
::before
{
border-width
:
0
5px
5px
;
border-bottom-color
:
$border-color
;
}
&
::before
,
&
::after
{
content
:
''
;
display
:
inline-block
;
position
:
absolute
;
width
:
0
;
height
:
0
;
border-color
:
transparent
;
border-style
:
solid
;
top
:
-6px
;
left
:
2px
;
border-width
:
0
5px
6px
;
}
&
::after
{
margin-top
:
1px
;
border-bottom-color
:
$white-light
;
}
&
::before
{
border-width
:
0
5px
5px
;
border-bottom-color
:
$border-color
;
}
&
::after
{
margin-top
:
1px
;
border-bottom-color
:
$white-light
;
}
}
...
...
app/views/shared/_mini_pipeline_graph.html.haml
浏览文件 @
e69732e2
...
...
@@ -11,8 +11,8 @@
=
icon
(
'caret-down'
)
%ul
.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
.arrow-up
.js-builds-dropdown-list.scrollable-menu
%li
.js-builds-dropdown-list.scrollable-menu
.js-builds-dropdown-loading.builds-dropdown-loading.hidden
%span
.fa.fa-spinner.fa-spin
%li
.js-builds-dropdown-loading.hidden
.text-center
%i
.fa.fa-spinner.fa-spin
{
'aria-hidden'
:
'true'
,
'aria-label'
:
'Loading'
}
changelogs/unreleased/31558-job-dropdown.yml
0 → 100644
浏览文件 @
e69732e2
---
title
:
Job dropdown of pipeline mini graph updates in realtime when its opened
merge_request
:
author
:
spec/javascripts/fixtures/mini_dropdown_graph.html.haml
浏览文件 @
e69732e2
...
...
@@ -3,7 +3,7 @@
Dropdown
%ul
.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
.js-builds-dropdown-list.scrollable-menu
%li
.js-builds-dropdown-list.scrollable-menu
.js-builds-dropdown-loading.
builds-dropdown-loading.hidden
%span
.fa.fa-spinner
.fa-spin
%li
.js-
builds-dropdown-loading.hidden
%span
.fa.fa-spinner
spec/javascripts/pipelines/stage_spec.js
浏览文件 @
e69732e2
import
Vue
from
'
vue
'
;
import
{
SUCCESS_SVG
}
from
'
~/ci_status_icons
'
;
import
Stage
from
'
~/pipelines/components/stage
'
;
import
stage
from
'
~/pipelines/components/stage.vue
'
;
describe
(
'
Pipelines stage component
'
,
()
=>
{
let
StageComponent
;
let
component
;
beforeEach
(()
=>
{
StageComponent
=
Vue
.
extend
(
stage
);
component
=
new
StageComponent
({
propsData
:
{
stage
:
{
status
:
{
group
:
'
success
'
,
icon
:
'
icon_status_success
'
,
title
:
'
success
'
,
},
dropdown_path
:
'
foo
'
,
},
updateDropdown
:
false
,
},
}).
$mount
();
});
function
minify
(
string
)
{
return
string
.
replace
(
/
\s
/g
,
''
);
}
it
(
'
should render a dropdown with the status icon
'
,
()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'
class
'
)).
toEqual
(
'
dropdown
'
);
expect
(
component
.
$el
.
querySelector
(
'
svg
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
button
'
).
getAttribute
(
'
data-toggle
'
)).
toEqual
(
'
dropdown
'
);
});
describe
(
'
Pipelines Stage
'
,
()
=>
{
describe
(
'
data
'
,
()
=>
{
let
stageReturnValue
;
describe
(
'
with successfull request
'
,
()
=>
{
const
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
({
html
:
'
foo
'
}),
{
status
:
200
,
}));
};
beforeEach
(()
=>
{
stageReturnValue
=
Stage
.
data
(
);
Vue
.
http
.
interceptors
.
push
(
interceptor
);
});
it
(
'
should return object with .builds and .spinner
'
,
()
=>
{
expect
(
stageReturnValue
).
toEqual
({
builds
:
''
,
spinner
:
'
<span class="fa fa-spinner fa-spin"></span>
'
,
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
,
);
});
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
svgHTML
'
,
function
()
{
let
stage
;
let
svgHTML
;
it
(
'
should render the received data
'
,
(
done
)
=>
{
component
.
$el
.
querySelector
(
'
button
'
).
click
();
beforeEach
(()
=>
{
stage
=
{
stage
:
{
status
:
{
icon
:
'
icon_status_success
'
}
}
};
svgHTML
=
Stage
.
computed
.
svgHTML
.
call
(
stage
);
});
it
(
"
should return the correct icon for the stage's status
"
,
()
=>
{
expect
(
svgHTML
).
toBe
(
SUCCESS_SVG
);
});
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-builds-dropdown-container ul
'
).
textContent
.
trim
(),
).
toEqual
(
'
foo
'
);
done
();
},
0
);
});
});
describe
(
'
when mounted
'
,
()
=>
{
let
StageComponent
;
let
renderedComponent
;
let
stage
;
describe
(
'
when request fails
'
,
()
=>
{
const
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
({}),
{
status
:
500
,
}));
};
beforeEach
(()
=>
{
stage
=
{
status
:
{
icon
:
'
icon_status_success
'
}
};
StageComponent
=
Vue
.
extend
(
Stage
);
renderedComponent
=
new
StageComponent
({
propsData
:
{
stage
,
},
}).
$mount
();
Vue
.
http
.
interceptors
.
push
(
interceptor
);
});
it
(
'
should render the correct status svg
'
,
()
=>
{
const
minifiedComponent
=
minify
(
renderedComponent
.
$el
.
outerHTML
);
const
expectedSVG
=
minify
(
SUCCESS_SVG
);
expect
(
minifiedComponent
).
toContain
(
expectedSVG
);
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
,
);
});
});
describe
(
'
when request fails
'
,
()
=>
{
it
(
'
closes dropdown
'
,
()
=>
{
spyOn
(
$
,
'
ajax
'
).
and
.
callFake
(
options
=>
options
.
error
());
const
StageComponent
=
Vue
.
extend
(
Stage
);
const
component
=
new
StageComponent
({
propsData
:
{
stage
:
{
status
:
{
icon
:
'
foo
'
}
}
},
}).
$mount
();
it
(
'
should close the dropdown
'
,
()
=>
{
component
.
$el
.
click
();
expect
(
component
.
$el
.
classList
.
contains
(
'
open
'
),
).
toEqual
(
false
);
setTimeout
(()
=>
{
expect
(
component
.
$el
.
classList
.
contains
(
'
open
'
)).
toEqual
(
false
);
},
0
);
});
});
});
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录