Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
64857a9b
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,发现更多精彩内容 >>
提交
64857a9b
编写于
3月 01, 2018
作者:
F
Filipa Lacerda
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Manage empty states in Pipelines page
Adds i18n Adds test Fix broken tests Fixes empty tab state for external CI
上级
67feb7cd
变更
17
展开全部
隐藏空白更改
内联
并排
Showing
17 changed file
with
996 addition
and
405 deletion
+996
-405
app/assets/javascripts/commit/pipelines/pipelines_table.vue
app/assets/javascripts/commit/pipelines/pipelines_table.vue
+11
-27
app/assets/javascripts/pages/projects/pipelines/index/index.js
...ssets/javascripts/pages/projects/pipelines/index/index.js
+14
-0
app/assets/javascripts/pipelines/components/blank_state.vue
app/assets/javascripts/pipelines/components/blank_state.vue
+32
-0
app/assets/javascripts/pipelines/components/empty_state.vue
app/assets/javascripts/pipelines/components/empty_state.vue
+34
-15
app/assets/javascripts/pipelines/components/nav_controls.vue
app/assets/javascripts/pipelines/components/nav_controls.vue
+33
-48
app/assets/javascripts/pipelines/components/pipelines.vue
app/assets/javascripts/pipelines/components/pipelines.vue
+163
-87
app/assets/javascripts/pipelines/mixins/pipelines.js
app/assets/javascripts/pipelines/mixins/pipelines.js
+11
-14
app/assets/javascripts/pipelines/stores/pipelines_store.js
app/assets/javascripts/pipelines/stores/pipelines_store.js
+6
-1
app/views/projects/pipelines/index.html.haml
app/views/projects/pipelines/index.html.haml
+5
-4
changelogs/unreleased/38587-pipelines-empty-state.yml
changelogs/unreleased/38587-pipelines-empty-state.yml
+5
-0
spec/features/projects/pipelines/pipelines_spec.rb
spec/features/projects/pipelines/pipelines_spec.rb
+34
-5
spec/javascripts/pipelines/blank_state_spec.js
spec/javascripts/pipelines/blank_state_spec.js
+29
-0
spec/javascripts/pipelines/empty_state_spec.js
spec/javascripts/pipelines/empty_state_spec.js
+16
-12
spec/javascripts/pipelines/error_state_spec.js
spec/javascripts/pipelines/error_state_spec.js
+0
-27
spec/javascripts/pipelines/nav_controls_spec.js
spec/javascripts/pipelines/nav_controls_spec.js
+18
-66
spec/javascripts/pipelines/pipelines_spec.js
spec/javascripts/pipelines/pipelines_spec.js
+579
-98
spec/javascripts/pipelines/pipelines_store_spec.js
spec/javascripts/pipelines/pipelines_store_spec.js
+6
-1
未找到文件。
app/assets/javascripts/commit/pipelines/pipelines_table.vue
浏览文件 @
64857a9b
...
...
@@ -20,10 +20,6 @@
type
:
String
,
required
:
true
,
},
emptyStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
...
...
@@ -45,23 +41,14 @@
},
computed
:
{
/**
* Empty state is only rendered if after the first request we receive no pipelines.
*
* @return {Boolean}
*/
shouldRenderEmptyState
()
{
return
!
this
.
state
.
pipelines
.
length
&&
!
this
.
isLoading
&&
this
.
hasMadeRequest
&&
!
this
.
hasError
;
},
shouldRenderTable
()
{
return
!
this
.
isLoading
&&
this
.
state
.
pipelines
.
length
>
0
&&
!
this
.
hasError
;
},
shouldRenderErrorState
()
{
return
this
.
hasError
&&
!
this
.
isLoading
;
},
},
created
()
{
this
.
service
=
new
PipelinesService
(
this
.
endpoint
);
...
...
@@ -92,25 +79,22 @@
<div
class=
"content-list pipelines"
>
<loading-icon
label=
"Loading pipelines
"
:label=
"s__('Pipelines|Loading Pipelines')
"
size=
"3"
v-if=
"isLoading"
class=
"prepend-top-20"
/>
<empty-state
v-if=
"shouldRenderEmptyState"
:help-page-path=
"helpPagePath"
:empty-state-svg-path=
"emptyStateSvgPath"
/>
<error-state
v-if=
"shouldRenderErrorState"
:error-state-svg-path=
"errorStateSvgPath"
<svg-blank-state
v-else-if=
"shouldRenderErrorState"
:svg-path=
"errorStateSvgPath"
:message=
"s__(`Pipelines|There was an error with fetching the pipelines.
Try again in a few moments or contact your support team.`)"
/>
<div
class=
"table-holder"
v-if=
"shouldRenderTable"
v-
else-
if=
"shouldRenderTable"
>
<pipelines-table-component
:pipelines=
"state.pipelines"
...
...
app/assets/javascripts/pages/projects/pipelines/index/index.js
浏览文件 @
64857a9b
...
...
@@ -2,6 +2,7 @@ import Vue from 'vue';
import
PipelinesStore
from
'
../../../../pipelines/stores/pipelines_store
'
;
import
pipelinesComponent
from
'
../../../../pipelines/components/pipelines.vue
'
;
import
Translate
from
'
../../../../vue_shared/translate
'
;
import
{
convertPermissionToBoolean
}
from
'
../../../../lib/utils/common_utils
'
;
Vue
.
use
(
Translate
);
...
...
@@ -15,12 +16,25 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
return
{
store
,
dataset
:
document
.
querySelector
(
this
.
$options
.
el
).
dataset
,
};
},
render
(
createElement
)
{
return
createElement
(
'
pipelines-component
'
,
{
props
:
{
store
:
this
.
store
,
endpoint
:
this
.
dataset
.
endpoint
,
helpPagePath
:
this
.
dataset
.
helpPagePath
,
emptyStateSvgPath
:
this
.
dataset
.
emptyStateSvgPath
,
errorStateSvgPath
:
this
.
dataset
.
errorStateSvgPath
,
noPipelinesSvgPath
:
this
.
dataset
.
noPipelinesSvgPath
,
autoDevopsPath
:
this
.
dataset
.
helpAutoDevopsPath
,
newPipelinePath
:
this
.
dataset
.
newPipelinePath
,
canCreatePipeline
:
convertPermissionToBoolean
(
this
.
dataset
.
canCreatePipeline
),
hasGitlabCi
:
convertPermissionToBoolean
(
this
.
dataset
.
hasGitlabCi
),
ciLintPath
:
this
.
dataset
.
ciLintPath
,
resetCachePath
:
this
.
dataset
.
resetCachePath
,
},
});
},
...
...
app/assets/javascripts/pipelines/components/
error
_state.vue
→
app/assets/javascripts/pipelines/components/
blank
_state.vue
浏览文件 @
64857a9b
<
script
>
export
default
{
props
:
{
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
export
default
{
name
:
'
PipelinesSvgState
'
,
props
:
{
svgPath
:
{
type
:
String
,
required
:
true
,
},
message
:
{
type
:
String
,
required
:
true
,
},
},
},
};
};
</
script
>
<
template
>
<div
class=
"row empty-state
js-pipelines-error-state
"
>
<div
class=
"row empty-state"
>
<div
class=
"col-xs-12"
>
<div
class=
"svg-content"
>
<img
:src=
"
errorStateSvgPath"
/>
<img
:src=
"
svgPath"
/>
</div>
</div>
<div
class=
"col-xs-12 text-center"
>
<div
class=
"text-content"
>
<h4>
The API failed to fetch the pipelines.
</h4>
<h4>
{{
message
}}
</h4>
</div>
</div>
</div>
...
...
app/assets/javascripts/pipelines/components/empty_state.vue
浏览文件 @
64857a9b
<
script
>
export
default
{
name
:
'
PipelinesEmptyState
'
,
props
:
{
helpPagePath
:
{
type
:
String
,
...
...
@@ -9,6 +10,10 @@
type
:
String
,
required
:
true
,
},
canSetCi
:
{
type
:
Boolean
,
required
:
true
,
},
},
};
</
script
>
...
...
@@ -22,22 +27,36 @@
<div
class=
"col-xs-12"
>
<div
class=
"text-content"
>
<h4
class=
"text-center"
>
{{
s__
(
"
Pipelines|Build with confidence
"
)
}}
</h4>
<p>
{{
s__
(
`Pipelines|Continous Integration can help
catch bugs by running your tests automatically,
while Continuous Deployment can help you deliver code to your product environment.`
)
}}
<template
v-if=
"canSetCi"
>
<h4
class=
"text-center"
>
{{
s__
(
'
Pipelines|Build with confidence
'
)
}}
</h4>
<p>
{{
s__
(
`Pipelines|Continous Integration can help
catch bugs by running your tests automatically,
while Continuous Deployment can help you deliver
code to your product environment.`
)
}}
</p>
<div
class=
"text-center"
>
<a
:href=
"helpPagePath"
class=
"btn btn-info js-get-started-pipelines"
>
{{
s__
(
'
Pipelines|Get started with Pipelines
'
)
}}
</a>
</div>
</
template
>
<p
v-else
class=
"text-center"
>
{{ s__('Pipelines|This project is not currently set up to run pipelines.') }}
</p>
<div
class=
"text-center"
>
<a
:href=
"helpPagePath"
class=
"btn btn-info"
>
{{
s__
(
"
Pipelines|Get started with Pipelines
"
)
}}
</a>
</div>
</div>
</div>
</div>
...
...
app/assets/javascripts/pipelines/components/nav_controls.vue
浏览文件 @
64857a9b
<
script
>
export
default
{
name
:
'
PipelineNavControls
'
,
props
:
{
newPipelinePath
:
{
type
:
String
,
required
:
true
,
export
default
{
name
:
'
PipelineNavControls
'
,
props
:
{
newPipelinePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
resetCachePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
ciLintPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
hasCiEnabled
:
{
type
:
Boolean
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
true
,
},
resetCachePath
:
{
type
:
String
,
required
:
true
,
},
ciLintPath
:
{
type
:
String
,
required
:
true
,
},
canCreatePipeline
:
{
type
:
Boolean
,
required
:
true
,
},
},
};
};
</
script
>
<
template
>
<div
class=
"nav-controls"
>
<a
v-if=
"
canCreatePipeline
"
v-if=
"
newPipelinePath
"
:href=
"newPipelinePath"
class=
"btn btn-create"
>
Run Pipeline
</a>
<a
v-if=
"!hasCiEnabled"
:href=
"helpPagePath"
class=
"btn btn-info"
>
Get started with Pipelines
class=
"btn btn-create js-run-pipeline"
>
{{
s__
(
'
Pipelines|Run Pipeline
'
)
}}
</a>
<a
v-if=
"resetCachePath"
data-method=
"post"
rel=
"nofollow"
:href=
"resetCachePath"
class=
"btn btn-default"
>
Clear runner caches
class=
"btn btn-default js-clear-cache"
>
{{
s__
(
'
Pipelines|Clear Runner Caches
'
)
}}
</a>
<a
v-if=
"ciLintPath"
:href=
"ciLintPath"
class=
"btn btn-default"
>
CI Lint
class=
"btn btn-default js-ci-lint"
>
{{
s__
(
'
Pipelines|CI Lint
'
)
}}
</a>
</div>
</
template
>
app/assets/javascripts/pipelines/components/pipelines.vue
浏览文件 @
64857a9b
<
script
>
import
_
from
'
underscore
'
;
import
{
__
,
sprintf
,
s__
}
from
'
../../locale
'
;
import
PipelinesService
from
'
../services/pipelines_service
'
;
import
pipelinesMixin
from
'
../mixins/pipelines
'
;
import
t
ablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
n
avigationTabs
from
'
../../vue_shared/components/navigation_tabs.vue
'
;
import
n
avigationControls
from
'
./nav_controls.vue
'
;
import
T
ablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
N
avigationTabs
from
'
../../vue_shared/components/navigation_tabs.vue
'
;
import
N
avigationControls
from
'
./nav_controls.vue
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
parseQueryStringIntoObject
,
}
from
'
../../lib/utils/common_utils
'
;
...
...
@@ -14,9 +14,9 @@
export
default
{
components
:
{
t
ablePagination
,
n
avigationTabs
,
n
avigationControls
,
T
ablePagination
,
N
avigationTabs
,
N
avigationControls
,
},
mixins
:
[
pipelinesMixin
,
...
...
@@ -36,111 +36,186 @@
required
:
false
,
default
:
'
root
'
,
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
true
,
},
emptyStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
noPipelinesSvgPath
:
{
type
:
String
,
required
:
true
,
},
autoDevopsPath
:
{
type
:
String
,
required
:
true
,
},
hasGitlabCi
:
{
type
:
Boolean
,
required
:
true
,
},
canCreatePipeline
:
{
type
:
Boolean
,
required
:
true
,
},
ciLintPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
resetCachePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
newPipelinePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
data
()
{
const
pipelinesData
=
document
.
querySelector
(
'
#pipelines-list-vue
'
).
dataset
;
return
{
endpoint
:
pipelinesData
.
endpoint
,
helpPagePath
:
pipelinesData
.
helpPagePath
,
emptyStateSvgPath
:
pipelinesData
.
emptyStateSvgPath
,
errorStateSvgPath
:
pipelinesData
.
errorStateSvgPath
,
autoDevopsPath
:
pipelinesData
.
helpAutoDevopsPath
,
newPipelinePath
:
pipelinesData
.
newPipelinePath
,
canCreatePipeline
:
pipelinesData
.
canCreatePipeline
,
hasCi
:
pipelinesData
.
hasCi
,
ciLintPath
:
pipelinesData
.
ciLintPath
,
resetCachePath
:
pipelinesData
.
resetCachePath
,
// Start with loading state to avoid a glitch when the empty state will be rendered
isLoading
:
true
,
state
:
this
.
store
.
state
,
scope
:
getParameterByName
(
'
scope
'
)
||
'
all
'
,
page
:
getParameterByName
(
'
page
'
)
||
'
1
'
,
requestData
:
{},
};
},
computed
:
{
canCreatePipelineParsed
()
{
return
convertPermissionToBoolean
(
this
.
canCreatePipeline
);
},
stateMap
:
{
// with tabs
loading
:
'
loading
'
,
tableList
:
'
tableList
'
,
error
:
'
error
'
,
emptyTab
:
'
emptyTab
'
,
// without tabs
emptyState
:
'
emptyState
'
,
},
scopes
:
{
all
:
'
all
'
,
pending
:
'
pending
'
,
running
:
'
running
'
,
finished
:
'
finished
'
,
branches
:
'
branches
'
,
tags
:
'
tags
'
,
},
computed
:
{
/**
* The empty state should only be rendered when the request is made to fetch all pipelines
* and none is returned.
*
* @return {Boolean}
*/
shouldRenderEmptyState
()
{
return
!
this
.
isLoading
&&
!
this
.
hasError
&&
this
.
hasMadeRequest
&&
!
this
.
state
.
pipelines
.
length
&&
(
this
.
scope
===
'
all
'
||
this
.
scope
===
null
);
* `hasGitlabCi` handles both internal and external CI.
* The order on which the checks are made in this method is
* important to guarantee we handle all the corner cases.
*/
stateToRender
()
{
const
{
stateMap
}
=
this
.
$options
;
if
(
this
.
isLoading
)
{
return
stateMap
.
loading
;
}
if
(
this
.
hasError
)
{
return
stateMap
.
error
;
}
if
(
this
.
state
.
pipelines
.
length
)
{
return
stateMap
.
tableList
;
}
if
(
this
.
hasGitlabCi
)
{
return
stateMap
.
emptyTab
;
}
return
stateMap
.
emptyState
;
},
/**
* When a specific scope does not have pipelines we render a message.
*
* @return {Boolean}
* Tabs are rendered in all states except empty state.
* They are not rendered before the first request to avoid a flicker on first load.
*/
shouldRenderNoPipelinesMessage
()
{
return
!
this
.
isLoading
&&
!
this
.
hasError
&&
!
this
.
state
.
pipelines
.
length
&&
this
.
scope
!==
'
all
'
&&
this
.
scope
!==
null
;
shouldRenderTabs
()
{
const
{
stateMap
}
=
this
.
$options
;
return
this
.
hasMadeRequest
&&
[
stateMap
.
loading
,
stateMap
.
tableList
,
stateMap
.
error
,
stateMap
.
emptyTab
,
].
includes
(
this
.
stateToRender
);
},
shouldRenderTable
()
{
return
!
this
.
hasError
&&
!
this
.
isLoading
&&
this
.
state
.
pipelines
.
length
;
shouldRenderButtons
()
{
return
(
this
.
newPipelinePath
||
this
.
resetCachePath
||
this
.
ciLintPath
)
&&
this
.
shouldRenderTabs
;
},
/**
* Pagination should only be rendered when there is more than one page.
*
* @return {Boolean}
*/
shouldRenderPagination
()
{
return
!
this
.
isLoading
&&
this
.
state
.
pipelines
.
length
&&
this
.
state
.
pageInfo
.
total
>
this
.
state
.
pageInfo
.
perPage
;
},
hasCiEnabled
()
{
return
this
.
hasCi
!==
undefined
;
emptyTabMessage
()
{
const
{
scopes
}
=
this
.
$options
;
const
possibleScopes
=
[
scopes
.
pending
,
scopes
.
running
,
scopes
.
finished
];
if
(
possibleScopes
.
includes
(
this
.
scope
))
{
return
sprintf
(
s__
(
'
Pipelines|There are currently no %{scope} pipelines.
'
),
{
scope
:
this
.
scope
,
});
}
return
s__
(
'
Pipelines|There are currently no pipelines.
'
);
},
tabs
()
{
const
{
count
}
=
this
.
state
;
const
{
scopes
}
=
this
.
$options
;
return
[
{
name
:
'
All
'
,
scope
:
'
all
'
,
name
:
__
(
'
All
'
)
,
scope
:
scopes
.
all
,
count
:
count
.
all
,
isActive
:
this
.
scope
===
'
all
'
,
},
{
name
:
'
Pending
'
,
scope
:
'
pending
'
,
name
:
__
(
'
Pending
'
)
,
scope
:
scopes
.
pending
,
count
:
count
.
pending
,
isActive
:
this
.
scope
===
'
pending
'
,
},
{
name
:
'
Running
'
,
scope
:
'
running
'
,
name
:
__
(
'
Running
'
)
,
scope
:
scopes
.
running
,
count
:
count
.
running
,
isActive
:
this
.
scope
===
'
running
'
,
},
{
name
:
'
Finished
'
,
scope
:
'
finished
'
,
name
:
__
(
'
Finished
'
)
,
scope
:
scopes
.
finished
,
count
:
count
.
finished
,
isActive
:
this
.
scope
===
'
finished
'
,
},
{
name
:
'
Branches
'
,
scope
:
'
branches
'
,
name
:
__
(
'
Branches
'
)
,
scope
:
scopes
.
branches
,
isActive
:
this
.
scope
===
'
branches
'
,
},
{
name
:
'
Tags
'
,
scope
:
'
tags
'
,
name
:
__
(
'
Tags
'
)
,
scope
:
scopes
.
tags
,
isActive
:
this
.
scope
===
'
tags
'
,
},
];
...
...
@@ -187,7 +262,7 @@
this
.
errorCallback
();
// restart polling
this
.
poll
.
restart
();
this
.
poll
.
restart
(
{
data
:
this
.
requestData
}
);
});
},
},
...
...
@@ -197,69 +272,70 @@
<div
class=
"pipelines-container"
>
<div
class=
"top-area scrolling-tabs-container inner-page-scroll-tabs"
v-if=
"
!shouldRenderEmptyState
"
v-if=
"
shouldRenderTabs || shouldRenderButtons
"
>
<div
class=
"fade-left"
>
<i
class=
"fa fa-angle-left"
aria-hidden=
"true"
>
aria-hidden=
"true"
>
</i>
</div>
<div
class=
"fade-right"
>
<i
class=
"fa fa-angle-right"
aria-hidden=
"true"
>
aria-hidden=
"true"
>
</i>
</div>
<navigation-tabs
v-if=
"shouldRenderTabs"
:tabs=
"tabs"
@
onChangeTab=
"onChangeTab"
scope=
"pipelines"
/>
<navigation-controls
v-if=
"shouldRenderButtons"
:new-pipeline-path=
"newPipelinePath"
:has-ci-enabled=
"hasCiEnabled"
:help-page-path=
"helpPagePath"
:reset-cache-path=
"resetCachePath"
:ci-lint-path=
"ciLintPath"
:can-create-pipeline=
"canCreatePipelineParsed "
/>
</div>
<div
class=
"content-list pipelines"
>
<loading-icon
label=
"Loading Pipelines"
v-if=
"stateToRender === $options.stateMap.loading"
:label=
"s__('Pipelines|Loading Pipelines')"
size=
"3"
v-if=
"isLoading"
class=
"prepend-top-20"
/>
<empty-state
v-
if=
"shouldRenderE
mptyState"
v-
else-if=
"stateToRender === $options.stateMap.e
mptyState"
:help-page-path=
"helpPagePath"
:empty-state-svg-path=
"emptyStateSvgPath"
:can-set-ci=
"canCreatePipeline"
/>
<error-state
v-if=
"shouldRenderErrorState"
:error-state-svg-path=
"errorStateSvgPath"
<svg-blank-state
v-else-if=
"stateToRender === $options.stateMap.error"
:svg-path=
"errorStateSvgPath"
:message=
"s__(`Pipelines|There was an error with fetching the pipelines.
Try again in a few moments or contact your support team.`)"
/>
<div
class=
"blank-state-row"
v-if=
"shouldRenderNoPipelinesMessage"
>
<div
class=
"blank-state-center"
>
<h2
class=
"blank-state-title js-blank-state-title"
>
No pipelines to show.
</h2>
</div>
</div>
<svg-blank-state
v-else-if=
"stateToRender === $options.stateMap.emptyTab"
:svg-path=
"noPipelinesSvgPath"
:message=
"emptyTabMessage"
/>
<div
class=
"table-holder"
v-
if=
"shouldRenderTable
"
v-
else-if=
"stateToRender === $options.stateMap.tableList
"
>
<pipelines-table-component
...
...
app/assets/javascripts/pipelines/mixins/pipelines.js
浏览文件 @
64857a9b
import
Visibility
from
'
visibilityjs
'
;
import
{
__
}
from
'
../../locale
'
;
import
Flash
from
'
../../flash
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
e
mptyState
from
'
../components/empty_state.vue
'
;
import
errorState
from
'
../components/error
_state.vue
'
;
import
l
oadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
p
ipelinesTableComponent
from
'
../components/pipelines_table.vue
'
;
import
E
mptyState
from
'
../components/empty_state.vue
'
;
import
SvgBlankState
from
'
../components/blank
_state.vue
'
;
import
L
oadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
P
ipelinesTableComponent
from
'
../components/pipelines_table.vue
'
;
import
eventHub
from
'
../event_hub
'
;
export
default
{
components
:
{
pipelinesTableComponent
,
errorState
,
emptyState
,
loadingIcon
,
},
computed
:
{
shouldRenderErrorState
()
{
return
this
.
hasError
&&
!
this
.
isLoading
;
},
PipelinesTableComponent
,
SvgBlankState
,
EmptyState
,
LoadingIcon
,
},
data
()
{
return
{
...
...
@@ -85,6 +81,7 @@ export default {
this
.
hasError
=
true
;
this
.
isLoading
=
false
;
this
.
updateGraphDropdown
=
false
;
this
.
hasMadeRequest
=
true
;
},
setIsMakingRequest
(
isMakingRequest
)
{
this
.
isMakingRequest
=
isMakingRequest
;
...
...
@@ -96,7 +93,7 @@ export default {
postAction
(
endpoint
)
{
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
eventHub
.
$emit
(
'
refreshPipelines
'
))
.
catch
(()
=>
new
Flash
(
'
An error occurred while making the request.
'
));
.
catch
(()
=>
Flash
(
__
(
'
An error occurred while making the request.
'
)
));
},
},
};
app/assets/javascripts/pipelines/stores/pipelines_store.js
浏览文件 @
64857a9b
...
...
@@ -5,7 +5,12 @@ export default class PipelinesStore {
this
.
state
=
{};
this
.
state
.
pipelines
=
[];
this
.
state
.
count
=
{};
this
.
state
.
count
=
{
all
:
0
,
finished
:
0
,
pending
:
0
,
running
:
0
,
};
this
.
state
.
pageInfo
=
{};
}
...
...
app/views/projects/pipelines/index.html.haml
浏览文件 @
64857a9b
...
...
@@ -7,8 +7,9 @@
"help-auto-devops-path"
=>
help_page_path
(
'topics/autodevops/index.md'
),
"empty-state-svg-path"
=>
image_path
(
'illustrations/pipelines_empty.svg'
),
"error-state-svg-path"
=>
image_path
(
'illustrations/pipelines_failed.svg'
),
"n
ew-pipeline-path"
=>
new_project_pipeline_path
(
@project
),
"n
o-pipelines-svg-path"
=>
image_path
(
'illustrations/pipelines_pending.svg'
),
"can-create-pipeline"
=>
can?
(
current_user
,
:create_pipeline
,
@project
).
to_s
,
"has-ci"
=>
@repository
.
gitlab_ci_yml
,
"ci-lint-path"
=>
ci_lint_path
,
"reset-cache-path"
=>
reset_cache_project_settings_ci_cd_path
(
@project
)
}
}
"new-pipeline-path"
=>
can?
(
current_user
,
:create_pipeline
,
@project
)
&&
new_project_pipeline_path
(
@project
),
"ci-lint-path"
=>
can?
(
current_user
,
:create_pipeline
,
@project
)
&&
ci_lint_path
,
"reset-cache-path"
=>
can?
(
current_user
,
:admin_pipeline
,
@project
)
&&
reset_cache_project_settings_ci_cd_path
(
@project
)
,
"has-gitlab-ci"
=>
(
@project
.
has_ci?
&&
@project
.
builds_enabled?
).
to_s
}
}
changelogs/unreleased/38587-pipelines-empty-state.yml
0 → 100644
浏览文件 @
64857a9b
---
title
:
Handle empty state in Pipelines page
merge_request
:
author
:
type
:
fixed
spec/features/projects/pipelines/pipelines_spec.rb
浏览文件 @
64857a9b
...
...
@@ -86,7 +86,22 @@ describe 'Pipelines', :js do
it
'updates content when tab is clicked'
do
page
.
find
(
'.js-pipelines-tab-pending'
).
click
wait_for_requests
expect
(
page
).
to
have_content
(
'No pipelines to show.'
)
expect
(
page
).
to
have_content
(
'There are currently no pending pipelines.'
)
end
end
context
'navigation links'
do
before
do
visit
project_pipelines_path
(
project
)
wait_for_requests
end
it
'renders run pipeline link'
do
expect
(
page
).
to
have_link
(
'Run Pipeline'
)
end
it
'renders ci lint link'
do
expect
(
page
).
to
have_link
(
'CI Lint'
)
end
end
...
...
@@ -542,7 +557,7 @@ describe 'Pipelines', :js do
end
it
'has a clear caches button'
do
expect
(
page
).
to
have_link
'Clear
runner c
aches'
expect
(
page
).
to
have_link
'Clear
Runner C
aches'
end
describe
'user clicks the button'
do
...
...
@@ -552,19 +567,31 @@ describe 'Pipelines', :js do
end
it
'increments jobs_cache_index'
do
click_link
'Clear
runner c
aches'
click_link
'Clear
Runner C
aches'
expect
(
page
.
find
(
'.flash-notice'
)).
to
have_content
'Project cache successfully reset.'
end
end
context
'when project does not have jobs_cache_index'
do
it
'sets jobs_cache_index to 1'
do
click_link
'Clear
runner c
aches'
click_link
'Clear
Runner C
aches'
expect
(
page
.
find
(
'.flash-notice'
)).
to
have_content
'Project cache successfully reset.'
end
end
end
end
describe
'Empty State'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
before
do
visit
project_pipelines_path
(
project
)
end
it
'renders empty state'
do
expect
(
page
).
to
have_content
'Build with confidence'
end
end
end
context
'when user is not logged in'
do
...
...
@@ -575,7 +602,9 @@ describe 'Pipelines', :js do
context
'when project is public'
do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
it
{
expect
(
page
).
to
have_content
'Build with confidence'
}
context
'without pipelines'
do
it
{
expect
(
page
).
to
have_content
'This project is not currently set up to run pipelines.'
}
end
end
context
'when project is private'
do
...
...
spec/javascripts/pipelines/blank_state_spec.js
0 → 100644
浏览文件 @
64857a9b
import
Vue
from
'
vue
'
;
import
component
from
'
~/pipelines/components/blank_state.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Pipelines Blank State
'
,
()
=>
{
let
vm
;
let
Component
;
beforeEach
(()
=>
{
Component
=
Vue
.
extend
(
component
);
vm
=
mountComponent
(
Component
,
{
svgPath
:
'
foo
'
,
message
:
'
Blank State
'
,
},
);
});
it
(
'
should render svg
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.svg-content img
'
).
getAttribute
(
'
src
'
)).
toEqual
(
'
foo
'
);
});
it
(
'
should render message
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
h4
'
).
textContent
.
trim
(),
).
toEqual
(
'
Blank State
'
);
});
});
spec/javascripts/pipelines/empty_state_spec.js
浏览文件 @
64857a9b
import
Vue
from
'
vue
'
;
import
emptyStateComp
from
'
~/pipelines/components/empty_state.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Pipelines Empty State
'
,
()
=>
{
let
component
;
...
...
@@ -8,12 +9,15 @@ describe('Pipelines Empty State', () => {
beforeEach
(()
=>
{
EmptyStateComponent
=
Vue
.
extend
(
emptyStateComp
);
component
=
new
EmptyStateComponent
({
propsData
:
{
helpPagePath
:
'
foo
'
,
emptyStateSvgPath
:
'
foo
'
,
},
}).
$mount
();
component
=
mountComponent
(
EmptyStateComponent
,
{
helpPagePath
:
'
foo
'
,
emptyStateSvgPath
:
'
foo
'
,
canSetCi
:
true
,
});
});
afterEach
(()
=>
{
component
.
$destroy
();
});
it
(
'
should render empty state SVG
'
,
()
=>
{
...
...
@@ -24,16 +28,16 @@ describe('Pipelines Empty State', () => {
expect
(
component
.
$el
.
querySelector
(
'
h4
'
).
textContent
).
toContain
(
'
Build with confidence
'
);
expect
(
component
.
$el
.
querySelector
(
'
p
'
).
textContent
.
trim
().
replace
(
/
[\r\n]
+/g
,
'
'
),
).
toContain
(
'
Continous Integration can help catch bugs by running your tests automatically
'
);
component
.
$el
.
querySelector
(
'
p
'
).
innerHTML
.
trim
().
replace
(
/
\n
+
\s
+/m
,
'
'
).
replace
(
/
\s\s
+/g
,
'
'
),
).
toContain
(
'
Continous Integration can help catch bugs by running your tests automatically
,
'
);
expect
(
component
.
$el
.
querySelector
(
'
p
'
).
textContent
.
trim
().
replace
(
/
[\r\n]
+/g
,
'
'
),
).
toContain
(
'
Continuous Deployment can help you deliver code to your product environment
'
);
component
.
$el
.
querySelector
(
'
p
'
).
innerHTML
.
trim
().
replace
(
/
\n
+
\s
+/m
,
'
'
).
replace
(
/
\s\s
+/g
,
'
'
),
).
toContain
(
'
while
Continuous Deployment can help you deliver code to your product environment
'
);
});
it
(
'
should render a link with provided help path
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.
btn-info
'
).
getAttribute
(
'
href
'
)).
toEqual
(
'
foo
'
);
expect
(
component
.
$el
.
querySelector
(
'
.
btn-info
'
).
textContent
).
toContain
(
'
Get started with Pipelines
'
);
expect
(
component
.
$el
.
querySelector
(
'
.
js-get-started-pipelines
'
).
getAttribute
(
'
href
'
)).
toEqual
(
'
foo
'
);
expect
(
component
.
$el
.
querySelector
(
'
.
js-get-started-pipelines
'
).
textContent
).
toContain
(
'
Get started with Pipelines
'
);
});
});
spec/javascripts/pipelines/error_state_spec.js
已删除
100644 → 0
浏览文件 @
67feb7cd
import
Vue
from
'
vue
'
;
import
errorStateComp
from
'
~/pipelines/components/error_state.vue
'
;
describe
(
'
Pipelines Error State
'
,
()
=>
{
let
component
;
let
ErrorStateComponent
;
beforeEach
(()
=>
{
ErrorStateComponent
=
Vue
.
extend
(
errorStateComp
);
component
=
new
ErrorStateComponent
({
propsData
:
{
errorStateSvgPath
:
'
foo
'
,
},
}).
$mount
();
});
it
(
'
should render error state SVG
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.svg-content svg
'
)).
toBeDefined
();
});
it
(
'
should render emtpy state information
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
h4
'
).
textContent
,
).
toContain
(
'
The API failed to fetch the pipelines
'
);
});
});
spec/javascripts/pipelines/nav_controls_spec.js
浏览文件 @
64857a9b
import
Vue
from
'
vue
'
;
import
navControlsComp
from
'
~/pipelines/components/nav_controls.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Pipelines Nav Controls
'
,
()
=>
{
let
NavControlsComponent
;
let
component
;
beforeEach
(()
=>
{
NavControlsComponent
=
Vue
.
extend
(
navControlsComp
);
});
afterEach
(()
=>
{
component
.
$destroy
();
});
it
(
'
should render link to create a new pipeline
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
true
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
true
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
component
=
mountComponent
(
NavControlsComponent
,
mockData
);
expect
(
component
.
$el
.
querySelector
(
'
.
btn-creat
e
'
).
textContent
).
toContain
(
'
Run Pipeline
'
);
expect
(
component
.
$el
.
querySelector
(
'
.
btn-creat
e
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
newPipelinePath
);
expect
(
component
.
$el
.
querySelector
(
'
.
js-run-pipelin
e
'
).
textContent
).
toContain
(
'
Run Pipeline
'
);
expect
(
component
.
$el
.
querySelector
(
'
.
js-run-pipelin
e
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
newPipelinePath
);
});
it
(
'
should not render link to create pipeline if no p
ermission
is provided
'
,
()
=>
{
it
(
'
should not render link to create pipeline if no p
ath
is provided
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
true
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
false
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
component
=
mountComponent
(
NavControlsComponent
,
mockData
);
expect
(
component
.
$el
.
querySelector
(
'
.
btn-creat
e
'
)).
toEqual
(
null
);
expect
(
component
.
$el
.
querySelector
(
'
.
js-run-pipelin
e
'
)).
toEqual
(
null
);
});
it
(
'
should render link for resetting runner caches
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
true
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
false
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
component
=
mountComponent
(
NavControlsComponent
,
mockData
);
expect
(
component
.
$el
.
querySelector
All
(
'
.btn-default
'
)[
0
].
textContent
).
toContain
(
'
Clear runner c
aches
'
);
expect
(
component
.
$el
.
querySelector
All
(
'
.btn-default
'
)[
0
]
.
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
resetCachePath
);
expect
(
component
.
$el
.
querySelector
(
'
.js-clear-cache
'
).
textContent
.
trim
()).
toContain
(
'
Clear Runner C
aches
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-clear-cache
'
)
.
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
resetCachePath
);
});
it
(
'
should render link for CI lint
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
true
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
true
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
expect
(
component
.
$el
.
querySelectorAll
(
'
.btn-default
'
)[
1
].
textContent
).
toContain
(
'
CI Lint
'
);
expect
(
component
.
$el
.
querySelectorAll
(
'
.btn-default
'
)[
1
].
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
ciLintPath
);
});
it
(
'
should render link to help page when CI is not enabled
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
false
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
true
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
expect
(
component
.
$el
.
querySelector
(
'
.btn-info
'
).
textContent
).
toContain
(
'
Get started with Pipelines
'
);
expect
(
component
.
$el
.
querySelector
(
'
.btn-info
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
helpPagePath
);
});
it
(
'
should not render link to help page when CI is enabled
'
,
()
=>
{
const
mockData
=
{
newPipelinePath
:
'
foo
'
,
hasCiEnabled
:
true
,
helpPagePath
:
'
foo
'
,
ciLintPath
:
'
foo
'
,
resetCachePath
:
'
foo
'
,
canCreatePipeline
:
true
,
};
const
component
=
new
NavControlsComponent
({
propsData
:
mockData
,
}).
$mount
();
component
=
mountComponent
(
NavControlsComponent
,
mockData
);
expect
(
component
.
$el
.
querySelector
(
'
.btn-info
'
)).
toEqual
(
null
);
expect
(
component
.
$el
.
querySelector
(
'
.js-ci-lint
'
).
textContent
.
trim
()).
toContain
(
'
CI Lint
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-ci-lint
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mockData
.
ciLintPath
);
});
});
spec/javascripts/pipelines/pipelines_spec.js
浏览文件 @
64857a9b
此差异已折叠。
点击以展开。
spec/javascripts/pipelines/pipelines_store_spec.js
浏览文件 @
64857a9b
...
...
@@ -9,7 +9,12 @@ describe('Pipelines Store', () => {
it
(
'
should be initialized with an empty state
'
,
()
=>
{
expect
(
store
.
state
.
pipelines
).
toEqual
([]);
expect
(
store
.
state
.
count
).
toEqual
({});
expect
(
store
.
state
.
count
).
toEqual
({
all
:
0
,
finished
:
0
,
pending
:
0
,
running
:
0
,
});
expect
(
store
.
state
.
pageInfo
).
toEqual
({});
});
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录