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
5e436de6
Commit
5e436de6
authored
May 07, 2018
by
Lukas Eipert
Committed by
Clement Ho
May 07, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make deploy keys table more clearly structured
parent
924ea97a
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
766 additions
and
518 deletions
+766
-518
app/assets/javascripts/deploy_keys/components/action_btn.vue
app/assets/javascripts/deploy_keys/components/action_btn.vue
+33
-38
app/assets/javascripts/deploy_keys/components/app.vue
app/assets/javascripts/deploy_keys/components/app.vue
+128
-84
app/assets/javascripts/deploy_keys/components/key.vue
app/assets/javascripts/deploy_keys/components/key.vue
+223
-99
app/assets/javascripts/deploy_keys/components/keys_panel.vue
app/assets/javascripts/deploy_keys/components/keys_panel.vue
+53
-47
app/assets/javascripts/deploy_keys/index.js
app/assets/javascripts/deploy_keys/index.js
+21
-18
app/assets/javascripts/deploy_keys/service/index.js
app/assets/javascripts/deploy_keys/service/index.js
+14
-11
app/assets/javascripts/deploy_keys/store/index.js
app/assets/javascripts/deploy_keys/store/index.js
+2
-2
app/assets/javascripts/vue_shared/components/icon.vue
app/assets/javascripts/vue_shared/components/icon.vue
+4
-1
app/assets/javascripts/vue_shared/components/navigation_tabs.vue
...ets/javascripts/vue_shared/components/navigation_tabs.vue
+44
-44
app/assets/stylesheets/pages/projects.scss
app/assets/stylesheets/pages/projects.scss
+34
-16
app/views/projects/deploy_keys/_index.html.haml
app/views/projects/deploy_keys/_index.html.haml
+1
-1
changelogs/unreleased/41082-make-deploykeys-table-more-clearly-structured.yml
...d/41082-make-deploykeys-table-more-clearly-structured.yml
+5
-0
features/steps/project/deploy_keys.rb
features/steps/project/deploy_keys.rb
+6
-1
spec/features/projects/deploy_keys_spec.rb
spec/features/projects/deploy_keys_spec.rb
+3
-3
spec/features/projects/settings/repository_settings_spec.rb
spec/features/projects/settings/repository_settings_spec.rb
+7
-3
spec/javascripts/deploy_keys/components/action_btn_spec.js
spec/javascripts/deploy_keys/components/action_btn_spec.js
+33
-31
spec/javascripts/deploy_keys/components/app_spec.js
spec/javascripts/deploy_keys/components/app_spec.js
+62
-64
spec/javascripts/deploy_keys/components/key_spec.js
spec/javascripts/deploy_keys/components/key_spec.js
+72
-30
spec/javascripts/deploy_keys/components/keys_panel_spec.js
spec/javascripts/deploy_keys/components/keys_panel_spec.js
+17
-25
spec/javascripts/fixtures/deploy_keys.rb
spec/javascripts/fixtures/deploy_keys.rb
+4
-0
No files found.
app/assets/javascripts/deploy_keys/components/action_btn.vue
View file @
5e436de6
<
script
>
import
eventHub
from
'
../eventhub
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
eventHub
from
'
../eventhub
'
;
export
default
{
components
:
{
loadingIcon
,
export
default
{
components
:
{
loadingIcon
,
},
props
:
{
deployKey
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
deployKey
:
{
type
:
Object
,
required
:
true
,
},
type
:
{
type
:
String
,
required
:
true
,
},
btnCssClass
:
{
type
:
String
,
required
:
false
,
default
:
'
btn-default
'
,
},
type
:
{
type
:
String
,
required
:
true
,
},
data
()
{
return
{
isLoading
:
false
,
};
btnCssClass
:
{
type
:
String
,
required
:
false
,
default
:
'
btn-default
'
,
},
computed
:
{
text
()
{
return
`
${
this
.
type
.
charAt
(
0
).
toUpperCase
()}${
this
.
type
.
slice
(
1
)}
`
;
},
},
methods
:
{
doAction
()
{
this
.
isLoading
=
true
;
},
data
()
{
return
{
isLoading
:
false
,
};
},
methods
:
{
doAction
()
{
this
.
isLoading
=
true
;
eventHub
.
$emit
(
`
${
this
.
type
}
.key`
,
this
.
deployKey
,
()
=>
{
this
.
isLoading
=
false
;
});
},
eventHub
.
$emit
(
`
${
this
.
type
}
.key`
,
this
.
deployKey
,
()
=>
{
this
.
isLoading
=
false
;
});
},
};
},
};
</
script
>
<
template
>
<button
class=
"btn
btn-sm prepend-left-10
"
class=
"btn"
:class=
"[
{ disabled: isLoading }, btnCssClass]"
:disabled="isLoading"
@click="doAction">
{{
text
}}
<slot></slot>
<loading-icon
v-if=
"isLoading"
:inline=
"true"
...
...
app/assets/javascripts/deploy_keys/components/app.vue
View file @
5e436de6
<
script
>
import
Flash
from
'
../../flash
'
;
import
eventHub
from
'
../eventhub
'
;
import
DeployKeysService
from
'
../service
'
;
import
DeployKeysStore
from
'
../store
'
;
import
keysPanel
from
'
./keys_panel.vue
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Flash
from
'
~/flash
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
NavigationTabs
from
'
~/vue_shared/components/navigation_tabs.vue
'
;
import
eventHub
from
'
../eventhub
'
;
import
DeployKeysService
from
'
../service
'
;
import
DeployKeysStore
from
'
../store
'
;
import
KeysPanel
from
'
./keys_panel.vue
'
;
export
default
{
components
:
{
keysPanel
,
loadingIcon
,
export
default
{
components
:
{
KeysPanel
,
LoadingIcon
,
NavigationTabs
,
},
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
String
,
required
:
true
,
},
data
()
{
return
{
isLoading
:
false
,
store
:
new
DeployKeysStore
(),
};
},
data
()
{
return
{
currentTab
:
'
enabled_keys
'
,
isLoading
:
false
,
store
:
new
DeployKeysStore
(),
};
},
scopes
:
{
enabled_keys
:
s__
(
'
DeployKeys|Enabled deploy keys
'
),
available_project_keys
:
s__
(
'
DeployKeys|Privately accessible deploy keys
'
),
public_keys
:
s__
(
'
DeployKeys|Publicly accessible deploy keys
'
),
},
computed
:
{
tabs
()
{
return
Object
.
keys
(
this
.
$options
.
scopes
).
map
(
scope
=>
{
const
count
=
Array
.
isArray
(
this
.
keys
[
scope
])
?
this
.
keys
[
scope
].
length
:
null
;
return
{
name
:
this
.
$options
.
scopes
[
scope
],
scope
,
isActive
:
scope
===
this
.
currentTab
,
count
,
};
});
},
hasKeys
()
{
return
Object
.
keys
(
this
.
keys
).
length
;
},
computed
:
{
hasKeys
()
{
return
Object
.
keys
(
this
.
keys
).
length
;
},
keys
()
{
return
this
.
store
.
keys
;
},
keys
()
{
return
this
.
store
.
keys
;
},
created
()
{
this
.
service
=
new
DeployKeysService
(
this
.
endpoint
);
},
created
()
{
this
.
service
=
new
DeployKeysService
(
this
.
endpoint
);
eventHub
.
$on
(
'
enable.key
'
,
this
.
enableKey
);
eventHub
.
$on
(
'
remove.key
'
,
this
.
disableKey
);
eventHub
.
$on
(
'
disable.key
'
,
this
.
disableKey
);
eventHub
.
$on
(
'
enable.key
'
,
this
.
enableKey
);
eventHub
.
$on
(
'
remove.key
'
,
this
.
disableKey
);
eventHub
.
$on
(
'
disable.key
'
,
this
.
disableKey
);
},
mounted
()
{
this
.
fetchKeys
();
},
beforeDestroy
()
{
eventHub
.
$off
(
'
enable.key
'
,
this
.
enableKey
);
eventHub
.
$off
(
'
remove.key
'
,
this
.
disableKey
);
eventHub
.
$off
(
'
disable.key
'
,
this
.
disableKey
);
},
methods
:
{
onChangeTab
(
tab
)
{
this
.
currentTab
=
tab
;
},
mounted
()
{
this
.
fetchKeys
();
fetchKeys
()
{
this
.
isLoading
=
true
;
return
this
.
service
.
getKeys
()
.
then
(
data
=>
{
this
.
isLoading
=
false
;
this
.
store
.
keys
=
data
;
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
this
.
store
.
keys
=
{};
return
new
Flash
(
s__
(
'
DeployKeys|Error getting deploy keys
'
));
});
},
beforeDestroy
()
{
eventHub
.
$off
(
'
enable.key
'
,
this
.
enableKey
);
eventHub
.
$off
(
'
remove.key
'
,
this
.
disableKey
);
eventHub
.
$off
(
'
disable.key
'
,
this
.
disableKey
);
enableKey
(
deployKey
)
{
this
.
service
.
enableKey
(
deployKey
.
id
)
.
then
(
this
.
fetchKeys
)
.
catch
(()
=>
new
Flash
(
s__
(
'
DeployKeys|Error enabling deploy key
'
)));
},
methods
:
{
fetchKeys
()
{
this
.
isLoading
=
true
;
this
.
service
.
getKeys
()
.
then
((
data
)
=>
{
this
.
isLoading
=
false
;
this
.
store
.
keys
=
data
;
})
.
catch
(()
=>
new
Flash
(
'
Error getting deploy keys
'
));
},
enableKey
(
deployKey
)
{
this
.
service
.
enableKey
(
deployKey
.
id
)
.
then
(()
=>
this
.
fetchKeys
())
.
catch
(()
=>
new
Flash
(
'
Error enabling deploy key
'
));
},
disableKey
(
deployKey
,
callback
)
{
// eslint-disable-next-line no-alert
if
(
confirm
(
'
You are going to remove this deploy key. Are you sure?
'
))
{
this
.
service
.
disableKey
(
deployKey
.
id
)
.
then
(()
=>
this
.
fetchKeys
())
.
then
(
callback
)
.
catch
(()
=>
new
Flash
(
'
Error removing deploy key
'
));
}
else
{
callback
();
}
},
disableKey
(
deployKey
,
callback
)
{
// eslint-disable-next-line no-alert
if
(
confirm
(
s__
(
'
DeployKeys|You are going to remove this deploy key. Are you sure?
'
)))
{
this
.
service
.
disableKey
(
deployKey
.
id
)
.
then
(
this
.
fetchKeys
)
.
then
(
callback
)
.
catch
(()
=>
new
Flash
(
s__
(
'
DeployKeys|Error removing deploy key
'
)));
}
else
{
callback
();
}
},
};
},
};
</
script
>
<
template
>
...
...
@@ -82,29 +117,38 @@
<loading-icon
v-if=
"isLoading && !hasKeys"
size=
"2"
label=
"Loading deploy keys
"
:label=
"s__('DeployKeys|Loading deploy keys')
"
/>
<div
v-else-if=
"hasKeys"
>
<template
v-else-if=
"hasKeys"
>
<div
class=
"top-area scrolling-tabs-container inner-page-scroll-tabs"
>
<div
class=
"fade-left"
>
<i
class=
"fa fa-angle-left"
aria-hidden=
"true"
>
</i>
</div>
<div
class=
"fade-right"
>
<i
class=
"fa fa-angle-right"
aria-hidden=
"true"
>
</i>
</div>
<navigation-tabs
:tabs=
"tabs"
@
onChangeTab=
"onChangeTab"
scope=
"deployKeys"
/>
</div>
<keys-panel
title=
"Enabled deploy keys for this project"
class=
"qa-project-deploy-keys"
:keys=
"keys.enabled_keys"
:store=
"store"
:endpoint=
"endpoint"
/>
<keys-panel
title=
"Deploy keys from projects you have access to"
:keys=
"keys.available_project_keys"
:store=
"store"
:endpoint=
"endpoint"
/>
<keys-panel
v-if=
"keys.public_keys.length"
title=
"Public deploy keys available to any project"
:keys=
"keys.public_keys"
:project-id=
"projectId"
:keys=
"keys[currentTab]"
:store=
"store"
:endpoint=
"endpoint"
/>
</
div
>
</
template
>
</div>
</template>
app/assets/javascripts/deploy_keys/components/key.vue
View file @
5e436de6
<
script
>
import
actionBtn
from
'
./action_btn.vue
'
;
import
{
getTimeago
}
from
'
../../lib/utils/datetime_utility
'
;
import
tooltip
from
'
../../vue_shared/directives/tooltip
'
;
import
_
from
'
underscore
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
export
default
{
components
:
{
actionBtn
,
},
directives
:
{
tooltip
,
},
props
:
{
deployKey
:
{
type
:
Object
,
required
:
true
,
},
store
:
{
type
:
Object
,
required
:
true
,
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
timeagoDate
()
{
return
getTimeago
().
format
(
this
.
deployKey
.
created_at
);
},
editDeployKeyPath
()
{
return
`
${
this
.
endpoint
}
/
${
this
.
deployKey
.
id
}
/edit`
;
},
},
methods
:
{
isEnabled
(
id
)
{
return
this
.
store
.
findEnabledKey
(
id
)
!==
undefined
;
},
tooltipTitle
(
project
)
{
return
project
.
can_push
?
'
Write access allowed
'
:
'
Read access only
'
;
},
},
};
import
actionBtn
from
'
./action_btn.vue
'
;
export
default
{
components
:
{
actionBtn
,
icon
,
},
directives
:
{
tooltip
,
},
mixins
:
[
timeagoMixin
],
props
:
{
deployKey
:
{
type
:
Object
,
required
:
true
,
},
store
:
{
type
:
Object
,
required
:
true
,
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
data
()
{
return
{
projectsExpanded
:
false
,
};
},
computed
:
{
editDeployKeyPath
()
{
return
`
${
this
.
endpoint
}
/
${
this
.
deployKey
.
id
}
/edit`
;
},
projects
()
{
const
projects
=
[...
this
.
deployKey
.
deploy_keys_projects
];
if
(
this
.
projectId
!==
null
)
{
const
indexOfCurrentProject
=
_
.
findIndex
(
projects
,
project
=>
project
&&
project
.
project
&&
project
.
project
.
id
&&
project
.
project
.
id
.
toString
()
===
this
.
projectId
,
);
if
(
indexOfCurrentProject
>
-
1
)
{
const
currentProject
=
projects
.
splice
(
indexOfCurrentProject
,
1
);
currentProject
[
0
].
project
.
full_name
=
s__
(
'
DeployKeys|Current project
'
);
return
currentProject
.
concat
(
projects
);
}
}
return
projects
;
},
firstProject
()
{
return
_
.
head
(
this
.
projects
);
},
restProjects
()
{
return
_
.
tail
(
this
.
projects
);
},
restProjectsTooltip
()
{
return
sprintf
(
s__
(
'
DeployKeys|Expand %{count} other projects
'
),
{
count
:
this
.
restProjects
.
length
,
});
},
restProjectsLabel
()
{
return
sprintf
(
s__
(
'
DeployKeys|+%{count} others
'
),
{
count
:
this
.
restProjects
.
length
});
},
isEnabled
()
{
return
this
.
store
.
isEnabled
(
this
.
deployKey
.
id
);
},
isRemovable
()
{
return
(
this
.
store
.
isEnabled
(
this
.
deployKey
.
id
)
&&
this
.
deployKey
.
destroyed_when_orphaned
&&
this
.
deployKey
.
almost_orphaned
);
},
isExpandable
()
{
return
!
this
.
projectsExpanded
&&
this
.
restProjects
.
length
>
1
;
},
isExpanded
()
{
return
this
.
projectsExpanded
||
this
.
restProjects
.
length
===
1
;
},
},
methods
:
{
projectTooltipTitle
(
project
)
{
return
project
.
can_push
?
s__
(
'
DeployKeys|Write access allowed
'
)
:
s__
(
'
DeployKeys|Read access only
'
);
},
toggleExpanded
()
{
this
.
projectsExpanded
=
!
this
.
projectsExpanded
;
},
},
};
</
script
>
<
template
>
<div>
<div
class=
"pull-left append-right-10 hidden-xs"
>
<i
aria-hidden=
"true"
class=
"fa fa-key key-icon"
>
</i>
<div
class=
"gl-responsive-table-row deploy-key"
>
<div
class=
"table-section section-40"
>
<div
role=
"rowheader"
class=
"table-mobile-header"
>
{{
s__
(
'
DeployKeys|Deploy key
'
)
}}
</div>
<div
class=
"table-mobile-content"
>
<strong
class=
"title qa-key-title"
>
{{
deployKey
.
title
}}
</strong>
<div
class=
"fingerprint qa-key-fingerprint"
>
{{
deployKey
.
fingerprint
}}
</div>
</div>
</div>
<div
class=
"deploy-key-content key-list-item-info"
>
<strong
class=
"title qa-key-title"
>
{{
deployKey
.
title
}}
</strong>
<div
class=
"description qa-key-fingerprint"
>
{{
deployKey
.
fingerprint
}}
<div
class=
"table-section section-30 section-wrap"
>
<div
role=
"rowheader"
class=
"table-mobile-header"
>
{{
s__
(
'
DeployKeys|Project usage
'
)
}}
</div>
<div
class=
"table-mobile-content deploy-project-list"
>
<template
v-if=
"projects.length > 0"
>
<a
class=
"label deploy-project-label"
:title=
"projectTooltipTitle(firstProject)"
v-tooltip
>
<span>
{{
firstProject
.
project
.
full_name
}}
</span>
<icon
:name=
"firstProject.can_push ? 'lock-open' : 'lock'"
/>
</a>
<a
v-if=
"isExpandable"
class=
"label deploy-project-label"
@
click=
"toggleExpanded"
:title=
"restProjectsTooltip"
v-tooltip
>
<span>
{{
restProjectsLabel
}}
</span>
</a>
<a
v-else-if=
"isExpanded"
v-for=
"deployKeysProject in restProjects"
:key=
"deployKeysProject.project.full_path"
class=
"label deploy-project-label"
:href=
"deployKeysProject.project.full_path"
:title=
"projectTooltipTitle(deployKeysProject)"
v-tooltip
>
<span>
{{
deployKeysProject
.
project
.
full_name
}}
</span>
<icon
:name=
"deployKeysProject.can_push ? 'lock-open' : 'lock'"
/>
</a>
</
template
>
<span
v-else
class=
"text-secondary"
>
{{ __('None') }}
</span>
</div>
</div>
<div
class=
"deploy-key-content prepend-left-default deploy-key-projects"
>
<a
v-for=
"(deployKeysProject, i) in deployKey.deploy_keys_projects"
:key=
"i"
class=
"label deploy-project-label"
:href=
"deployKeysProject.project.full_path"
:title=
"tooltipTitle(deployKeysProject)"
v-tooltip
>
{{
deployKeysProject
.
project
.
full_name
}}
<i
v-if=
"!deployKeysProject.can_push"
aria-hidden=
"true"
class=
"fa fa-lock"
>
</i>
</a>
<div
class=
"table-section section-15 text-right"
>
<div
role=
"rowheader"
class=
"table-mobile-header"
>
{{ __('Created') }}
</div>
<div
class=
"table-mobile-content text-secondary key-created-at"
>
<span
:title=
"tooltipTitle(deployKey.created_at)"
v-tooltip
>
<icon
name=
"calendar"
/>
<span>
{{ timeFormated(deployKey.created_at) }}
</span>
</span>
</div>
</div>
<div
class=
"deploy-key-content"
>
<span
class=
"key-created-at"
>
created
{{
timeagoDate
}}
</span>
<a
v-if=
"deployKey.can_edit"
class=
"btn btn-sm"
:href=
"editDeployKeyPath"
>
Edit
</a>
<action-btn
v-if=
"!isEnabled(deployKey.id)"
:deploy-key=
"deployKey"
type=
"enable"
/>
<action-btn
v-else-if=
"deployKey.destroyed_when_orphaned && deployKey.almost_orphaned"
:deploy-key=
"deployKey"
btn-css-class=
"btn-warning"
type=
"remove"
/>
<action-btn
v-else
:deploy-key=
"deployKey"
btn-css-class=
"btn-warning"
type=
"disable"
/>
<div
class=
"table-section section-15 table-button-footer deploy-key-actions"
>
<div
class=
"btn-group table-action-buttons"
>
<action-btn
v-if=
"!isEnabled"
:deploy-key=
"deployKey"
type=
"enable"
>
{{ __('Enable') }}
</action-btn>
<a
v-if=
"deployKey.can_edit"
class=
"btn btn-default text-secondary"
:href=
"editDeployKeyPath"
:title=
"__('Edit')"
data-container=
"body"
v-tooltip
>
<icon
name=
"pencil"
/>
</a>
<action-btn
v-if=
"isRemovable"
:deploy-key=
"deployKey"
btn-css-class=
"btn-danger"
type=
"remove"
:title=
"__('Remove')"
data-container=
"body"
v-tooltip
>
<icon
name=
"remove"
/>
</action-btn>
<action-btn
v-else-if=
"isEnabled"
:deploy-key=
"deployKey"
btn-css-class=
"btn-warning"
type=
"disable"
:title=
"__('Disable')"
data-container=
"body"
v-tooltip
>
<icon
name=
"cancel"
/>
</action-btn>
</div>
</div>
</div>
</template>
app/assets/javascripts/deploy_keys/components/keys_panel.vue
View file @
5e436de6
<
script
>
import
k
ey
from
'
./key.vue
'
;
import
deployK
ey
from
'
./key.vue
'
;
export
default
{
components
:
{
key
,
export
default
{
components
:
{
deployKey
,
},
props
:
{
keys
:
{
type
:
Array
,
required
:
true
,
},
props
:
{
title
:
{
type
:
String
,
required
:
true
,
},
keys
:
{
type
:
Array
,
required
:
true
,
},
showHelpBox
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
store
:
{
type
:
Object
,
required
:
true
,
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
store
:
{
type
:
Object
,
required
:
true
,
},
};
endpoint
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
};
</
script
>
<
template
>
<div
class=
"deploy-keys-panel"
>
<h5>
{{
title
}}
(
{{
keys
.
length
}}
)
</h5>
<ul
class=
"well-list"
v-if=
"keys.length"
>
<li
<div
class=
"deploy-keys-panel table-holder"
>
<template
v-if=
"keys.length > 0"
>
<div
role=
"row"
class=
"gl-responsive-table-row table-row-header"
>
<div
role=
"rowheader"
class=
"table-section section-40"
>
{{
s__
(
'
DeployKeys|Deploy key
'
)
}}
</div>
<div
role=
"rowheader"
class=
"table-section section-30"
>
{{
s__
(
'
DeployKeys|Project usage
'
)
}}
</div>
<div
role=
"rowheader"
class=
"table-section section-15 text-right"
>
{{
__
(
'
Created
'
)
}}
</div>
</div>
<deploy-key
v-for=
"deployKey in keys"
:key=
"deployKey.id"
>
<key
:deploy-key=
"deployKey"
:store=
"store"
:endpoint=
"endpoint"
/>
</li>
</ul>
:deploy-key=
"deployKey"
:store=
"store"
:endpoint=
"endpoint"
:project-id=
"projectId"
/>
</
template
>
<div
class=
"settings-message text-center"
v-else
-if=
"showHelpBox"
v-else
>
No deploy keys found. Create one with the form above.
{{ s__('DeployKeys|No deploy keys found. Create one with the form above.') }}
</div>
</div>
</template>
app/assets/javascripts/deploy_keys/index.js
View file @
5e436de6
import
Vue
from
'
vue
'
;
import
deployKeysApp
from
'
./components/app.vue
'
;
export
default
()
=>
new
Vue
({
el
:
document
.
getElementById
(
'
js-deploy-keys
'
),
components
:
{
deployKeysApp
,
},
data
()
{
return
{
endpoint
:
this
.
$options
.
el
.
dataset
.
endpoint
,
};
},
render
(
createElement
)
{
return
createElement
(
'
deploy-keys-app
'
,
{
props
:
{
endpoint
:
this
.
endpoint
,
},
});
},
});
export
default
()
=>
new
Vue
({
el
:
document
.
getElementById
(
'
js-deploy-keys
'
),
components
:
{
deployKeysApp
,
},
data
()
{
return
{
endpoint
:
this
.
$options
.
el
.
dataset
.
endpoint
,
projectId
:
this
.
$options
.
el
.
dataset
.
projectId
,
};
},
render
(
createElement
)
{
return
createElement
(
'
deploy-keys-app
'
,
{
props
:
{
endpoint
:
this
.
endpoint
,
projectId
:
this
.
projectId
,
},
});
},
});
app/assets/javascripts/deploy_keys/service/index.js
View file @
5e436de6
...
...
@@ -7,21 +7,24 @@ export default class DeployKeysService {
constructor
(
endpoint
)
{
this
.
endpoint
=
endpoint
;
this
.
resource
=
Vue
.
resource
(
`
${
this
.
endpoint
}
{/id}`
,
{},
{
enable
:
{
method
:
'
PUT
'
,
url
:
`
${
this
.
endpoint
}
{/id}/enable`
,
this
.
resource
=
Vue
.
resource
(
`
${
this
.
endpoint
}
{/id}`
,
{},
{
enable
:
{
method
:
'
PUT
'
,
url
:
`
${
this
.
endpoint
}
{/id}/enable`
,
},
disable
:
{
method
:
'
PUT
'
,
url
:
`
${
this
.
endpoint
}
{/id}/disable`
,
},
},
disable
:
{
method
:
'
PUT
'
,
url
:
`
${
this
.
endpoint
}
{/id}/disable`
,
},
});
);
}
getKeys
()
{
return
this
.
resource
.
get
()
.
then
(
response
=>
response
.
json
());
return
this
.
resource
.
get
().
then
(
response
=>
response
.
json
());
}
enableKey
(
id
)
{
...
...
app/assets/javascripts/deploy_keys/store/index.js
View file @
5e436de6
...
...
@@ -3,7 +3,7 @@ export default class DeployKeysStore {
this
.
keys
=
{};
}
findEnabledKey
(
id
)
{
return
this
.
keys
.
enabled_keys
.
find
(
key
=>
key
.
id
===
id
);
isEnabled
(
id
)
{
return
this
.
keys
.
enabled_keys
.
some
(
key
=>
key
.
id
===
id
);
}
}
app/assets/javascripts/vue_shared/components/icon.vue
View file @
5e436de6
...
...
@@ -65,6 +65,9 @@ export default {
spriteHref
()
{
return
`
${
gon
.
sprite_icons
}
#
${
this
.
name
}
`
;
},
iconTestClass
()
{
return
`ic-
${
this
.
name
}
`
;
},
iconSizeClass
()
{
return
this
.
size
?
`s
${
this
.
size
}
`
:
''
;
},
...
...
@@ -74,7 +77,7 @@ export default {
<
template
>
<svg
:class=
"[iconSizeClass, cssClasses]"
:class=
"[iconSizeClass,
iconTestClass,
cssClasses]"
:width=
"width"
:height=
"height"
:x=
"x"
...
...
app/assets/javascripts/vue_shared/components/navigation_tabs.vue
View file @
5e436de6
<
script
>
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
*
* This component is used in apps that handle the API call.
* If you only need to change the URL this component should not be used.
*
* @example
* <navigation-tabs
* :tabs="[
* {
* name: String,
* scope: String,
* count: Number || Undefined,
* isActive: Boolean,
* },
* ]"
* @onChangeTab="onChangeTab"
* />
*/
export
default
{
name
:
'
NavigationTabs
'
,
props
:
{
tabs
:
{
type
:
Array
,
required
:
true
,
},
scope
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
*
* This component is used in apps that handle the API call.
* If you only need to change the URL this component should not be used.
*
* @example
* <navigation-tabs
* :tabs="[
* {
* name: String,
* scope: String,
* count: Number || Undefined || Null,
* isActive: Boolean,
* },
* ]"
* @onChangeTab="onChangeTab"
* />
*/
export
default
{
name
:
'
NavigationTabs
'
,
props
:
{
tabs
:
{
type
:
Array
,
required
:
true
,
},
mounted
()
{
$
(
document
).
trigger
(
'
init.scrolling-tabs
'
);
scope
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
mounted
()
{
$
(
document
).
trigger
(
'
init.scrolling-tabs
'
);
},
methods
:
{
shouldRenderBadge
(
count
)
{
// 0 is valid in a badge, but evaluates to false, we need to check for undefined or null
return
!
(
count
===
undefined
||
count
===
null
);
},
methods
:
{
shouldRenderBadge
(
count
)
{
// 0 is valid in a badge, but evaluates to false, we need to check for undefined
return
count
!==
undefined
;
},
onTabClick
(
tab
)
{
this
.
$emit
(
'
onChangeTab
'
,
tab
.
scope
);
},
onTabClick
(
tab
)
{
this
.
$emit
(
'
onChangeTab
'
,
tab
.
scope
);
},
};
},
};
</
script
>
<
template
>
<ul
class=
"nav-links scrolling-tabs separator"
>
...
...
app/assets/stylesheets/pages/projects.scss
View file @
5e436de6
...
...
@@ -354,30 +354,48 @@
min-width
:
200px
;
}
.deploy-key-content
{
@media
(
min-width
:
$screen-sm-min
)
{
float
:
left
;
.deploy-keys
{
.scrolling-tabs-container
{
position
:
relative
;
}
}
&
:last-child
{
float
:
right
;
.deploy-key
{
// Ensure that the fingerprint does not overflow on small screens
.fingerprint
{
word-break
:
break-all
;
white-space
:
normal
;
}
.deploy-project-label
,
.key-created-at
{
svg
{
vertical-align
:
text-top
;
}
}
}
.deploy-key-projects
{
@media
(
min-width
:
$screen-sm-min
)
{
line-height
:
42px
;
.btn
svg
{
vertical-align
:
top
;
}
.key-created-at
{
line-height
:
unset
;
}
}
a
.deploy-project-label
{
padding
:
5px
;
margin-right
:
5px
;
color
:
$gl-text-color
;
background-color
:
$row-hover
;
.deploy-project-list
{
margin-bottom
:
-
$gl-padding-4
;
&
:hover
{
color
:
$gl-link-color
;
a
.deploy-project-label
{
margin-right
:
$gl-padding-4
;
margin-bottom
:
$gl-padding-4
;
color
:
$gl-text-color-secondary
;
background-color
:
$theme-gray-100
;
line-height
:
$gl-btn-line-height
;
&
:hover
{
color
:
$gl-link-color
;
}
}
}
...
...
app/views/projects/deploy_keys/_index.html.haml
View file @
5e436de6
...
...
@@ -12,4 +12,4 @@
Create a new deploy key for this project
=
render
@deploy_keys
.
form_partial_path
%hr
#js-deploy-keys
{
data:
{
endpoint:
project_deploy_keys_path
(
@project
)
}
}
#js-deploy-keys
{
data:
{
endpoint:
project_deploy_keys_path
(
@project
)
,
project_id:
@project
.
id
}
}
changelogs/unreleased/41082-make-deploykeys-table-more-clearly-structured.yml
0 → 100644
View file @
5e436de6
---
title
:
Make project deploy keys table more clearly structured
merge_request
:
18279
author
:
type
:
changed
features/steps/project/deploy_keys.rb
View file @
5e436de6
...
...
@@ -9,18 +9,21 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step
'I should see project deploy key'
do
page
.
within
(
find
(
'.deploy-keys'
))
do
find
(
'.js-deployKeys-tab-enabled_keys'
).
click
()
expect
(
page
).
to
have_content
deploy_key
.
title
end
end
step
'I should see other project deploy key'
do
page
.
within
(
find
(
'.deploy-keys'
))
do
find
(
'.js-deployKeys-tab-available_project_keys'
).
click
()
expect
(
page
).
to
have_content
other_deploy_key
.
title
end
end
step
'I should see public deploy key'
do
page
.
within
(
find
(
'.deploy-keys'
))
do
find
(
'.js-deployKeys-tab-public_keys'
).
click
()
expect
(
page
).
to
have_content
public_deploy_key
.
title
end
end
...
...
@@ -42,6 +45,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step
'I should see newly created deploy key'
do
@project
.
reload
page
.
within
(
find
(
'.deploy-keys'
))
do
find
(
'.js-deployKeys-tab-enabled_keys'
).
click
()
expect
(
page
).
to
have_content
(
deploy_key
.
title
)
end
end
...
...
@@ -58,7 +62,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step
'I should only see the same deploy key once'
do
page
.
within
(
find
(
'.deploy-keys'
))
do
expect
(
page
).
to
have_selector
(
'ul li'
,
count:
1
)
expect
(
find
(
'.js-deployKeys-tab-available_project_keys .badge'
)).
to
have_content
(
'1'
)
end
end
...
...
@@ -68,6 +72,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step
'I click attach deploy key'
do
page
.
within
(
find
(
'.deploy-keys'
))
do
find
(
'.badge'
,
text:
'1'
).
click
()
click_button
'Enable'
expect
(
page
).
not_to
have_selector
(
'.fa-spinner'
)
end
...
...
spec/features/projects/deploy_keys_spec.rb
View file @
5e436de6
...
...
@@ -18,12 +18,12 @@ describe 'Project deploy keys', :js do
visit
project_settings_repository_path
(
project
)
page
.
within
(
find
(
'.deploy-keys'
))
do
expect
(
page
).
to
have_selector
(
'.deploy-key
s li
'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.deploy-key'
,
count:
1
)
accept_confirm
{
find
(
:button
,
text:
'Remove'
).
send_keys
(
:return
)
}
accept_confirm
{
find
(
'.ic-remove'
).
click
(
)
}
expect
(
page
).
not_to
have_selector
(
'.fa-spinner'
,
count:
0
)
expect
(
page
).
to
have_selector
(
'.deploy-key
s li
'
,
count:
0
)
expect
(
page
).
to
have_selector
(
'.deploy-key'
,
count:
0
)
end
end
end
...
...
spec/features/projects/settings/repository_settings_spec.rb
View file @
5e436de6
...
...
@@ -54,7 +54,7 @@ describe 'Projects > Settings > Repository settings' do
project
.
deploy_keys
<<
private_deploy_key
visit
project_settings_repository_path
(
project
)
find
(
'
li'
,
text:
private_deploy_key
.
title
).
click_link
(
'Edit'
)
find
(
'
.deploy-key'
,
text:
private_deploy_key
.
title
).
find
(
'.ic-pencil'
).
click
(
)
fill_in
'deploy_key_title'
,
with:
'updated_deploy_key'
check
'deploy_key_deploy_keys_projects_attributes_0_can_push'
...
...
@@ -71,11 +71,15 @@ describe 'Projects > Settings > Repository settings' do
visit
project_settings_repository_path
(
project
)
find
(
'li'
,
text:
private_deploy_key
.
title
).
click_link
(
'Edit'
)
find
(
'.js-deployKeys-tab-available_project_keys'
).
click
()
find
(
'.deploy-key'
,
text:
private_deploy_key
.
title
).
find
(
'.ic-pencil'
).
click
()
fill_in
'deploy_key_title'
,
with:
'updated_deploy_key'
click_button
'Save changes'
find
(
'.js-deployKeys-tab-available_project_keys'
).
click
()
expect
(
page
).
to
have_content
(
'updated_deploy_key'
)
end
...
...
@@ -83,7 +87,7 @@ describe 'Projects > Settings > Repository settings' do
project
.
deploy_keys
<<
private_deploy_key
visit
project_settings_repository_path
(
project
)
accept_confirm
{
find
(
'
li'
,
text:
private_deploy_key
.
title
).
click_button
(
'Remove'
)
}
accept_confirm
{
find
(
'
.deploy-key'
,
text:
private_deploy_key
.
title
).
find
(
'.ic-remove'
).
click
(
)
}
expect
(
page
).
not_to
have_content
(
private_deploy_key
.
title
)
end
...
...
spec/javascripts/deploy_keys/components/action_btn_spec.js
View file @
5e436de6
...
...
@@ -7,62 +7,64 @@ describe('Deploy keys action btn', () => {
const
deployKey
=
data
.
enabled_keys
[
0
];
let
vm
;
beforeEach
((
done
)
=>
{
const
ActionBtnComponent
=
Vue
.
extend
(
actionBtn
);
vm
=
new
ActionBtnComponent
({
propsData
:
{
deployKey
,
type
:
'
enable
'
,
beforeEach
(
done
=>
{
const
ActionBtnComponent
=
Vue
.
extend
({
components
:
{
actionBtn
,
},
data
()
{
return
{
deployKey
,
};
},
}).
$mount
();
template
:
`
<action-btn
:deploy-key="deployKey"
type="enable">
Enable
</action-btn>`
,
});
vm
=
new
ActionBtnComponent
().
$mount
();
setTimeout
(
done
);
Vue
.
nextTick
()
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders the type as uppercase
'
,
()
=>
{
expect
(
vm
.
$el
.
textContent
.
trim
(),
).
toBe
(
'
Enable
'
);
it
(
'
renders the default slot
'
,
()
=>
{
expect
(
vm
.
$el
.
textContent
.
trim
()).
toBe
(
'
Enable
'
);
});
it
(
'
sends eventHub event with btn type
'
,
(
done
)
=>
{
it
(
'
sends eventHub event with btn type
'
,
done
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
vm
.
$el
.
click
();
setTimeout
(()
=>
{
expect
(
eventHub
.
$emit
,
).
toHaveBeenCalledWith
(
'
enable.key
'
,
deployKey
,
jasmine
.
anything
());
Vue
.
nextTick
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
enable.key
'
,
deployKey
,
jasmine
.
anything
());
done
();
});
});
it
(
'
shows loading spinner after click
'
,
(
done
)
=>
{
it
(
'
shows loading spinner after click
'
,
done
=>
{
vm
.
$el
.
click
();
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa
'
),
).
toBeDefined
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa
'
)).
toBeDefined
();
done
();
});
});
it
(
'
disables button after click
'
,
(
done
)
=>
{
it
(
'
disables button after click
'
,
done
=>
{
vm
.
$el
.
click
();
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
disabled
'
),
).
toBeTruthy
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
disabled
'
)).
toBeTruthy
();
expect
(
vm
.
$el
.
getAttribute
(
'
disabled
'
),
).
toBe
(
'
disabled
'
);
expect
(
vm
.
$el
.
getAttribute
(
'
disabled
'
)).
toBe
(
'
disabled
'
);
done
();
});
...
...
spec/javascripts/deploy_keys/components/app_spec.js
View file @
5e436de6
...
...
@@ -8,12 +8,14 @@ describe('Deploy keys app component', () => {
let
vm
;
const
deployKeysResponse
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
data
),
{
status
:
200
,
}));
next
(
request
.
respondWith
(
JSON
.
stringify
(
data
),
{
status
:
200
,
}),
);
};
beforeEach
(
(
done
)
=>
{
beforeEach
(
done
=>
{
const
Component
=
Vue
.
extend
(
deployKeysApp
);
Vue
.
http
.
interceptors
.
push
(
deployKeysResponse
);
...
...
@@ -21,6 +23,7 @@ describe('Deploy keys app component', () => {
vm
=
new
Component
({
propsData
:
{
endpoint
:
'
/test
'
,
projectId
:
'
8
'
,
},
}).
$mount
();
...
...
@@ -31,117 +34,112 @@ describe('Deploy keys app component', () => {
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
deployKeysResponse
);
});
it
(
'
renders loading icon
'
,
(
done
)
=>
{
it
(
'
renders loading icon
'
,
done
=>
{
vm
.
store
.
keys
=
{};
vm
.
isLoading
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys-panel
'
).
length
,
).
toBe
(
0
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys .nav-links li
'
).
length
).
toBe
(
0
);
expect
(
vm
.
$el
.
querySelector
(
'
.fa-spinner
'
),
).
toBeDefined
();
expect
(
vm
.
$el
.
querySelector
(
'
.fa-spinner
'
)).
toBeDefined
();
done
();
});
});
it
(
'
renders keys panels
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys-panel
'
).
length
,
).
toBe
(
3
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys .nav-links li
'
).
length
).
toBe
(
3
);
});
it
(
'
does not render key panels when keys object is empty
'
,
(
done
)
=>
{
vm
.
store
.
keys
=
{};
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys-panel
'
).
length
,
).
toBe
(
0
);
done
();
});
it
(
'
renders the titles with keys count
'
,
()
=>
{
const
textContent
=
selector
=>
{
const
element
=
vm
.
$el
.
querySelector
(
`
${
selector
}
`
);
expect
(
element
).
not
.
toBeNull
();
return
element
.
textContent
.
trim
();
};
expect
(
textContent
(
'
.js-deployKeys-tab-enabled_keys
'
)).
toContain
(
'
Enabled deploy keys
'
);
expect
(
textContent
(
'
.js-deployKeys-tab-available_project_keys
'
)).
toContain
(
'
Privately accessible deploy keys
'
,
);
expect
(
textContent
(
'
.js-deployKeys-tab-public_keys
'
)).
toContain
(
'
Publicly accessible deploy keys
'
,
);
expect
(
textContent
(
'
.js-deployKeys-tab-enabled_keys .badge
'
)).
toBe
(
`
${
vm
.
store
.
keys
.
enabled_keys
.
length
}
`
,
);
expect
(
textContent
(
'
.js-deployKeys-tab-available_project_keys .badge
'
)).
toBe
(
`
${
vm
.
store
.
keys
.
available_project_keys
.
length
}
`
,
);
expect
(
textContent
(
'
.js-deployKeys-tab-public_keys .badge
'
)).
toBe
(
`
${
vm
.
store
.
keys
.
public_keys
.
length
}
`
,
);
});
it
(
'
does not render
public panel when empty
'
,
(
done
)
=>
{
vm
.
store
.
keys
.
public_keys
=
[]
;
it
(
'
does not render
key panels when keys object is empty
'
,
done
=>
{
vm
.
store
.
keys
=
{}
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys-panel
'
).
length
,
).
toBe
(
2
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-keys .nav-links li
'
).
length
).
toBe
(
0
);
done
();
});
});
it
(
'
re-fetches deploy keys when enabling a key
'
,
(
done
)
=>
{
it
(
'
re-fetches deploy keys when enabling a key
'
,
done
=>
{
const
key
=
data
.
public_keys
[
0
];
spyOn
(
vm
.
service
,
'
getKeys
'
);
spyOn
(
vm
.
service
,
'
enableKey
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
)
=>
{
resolve
();
setTimeout
(()
=>
{
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
}));
spyOn
(
vm
.
service
,
'
enableKey
'
).
and
.
callFake
(()
=>
Promise
.
resolve
());
eventHub
.
$emit
(
'
enable.key
'
,
key
);
expect
(
vm
.
service
.
enableKey
).
toHaveBeenCalledWith
(
key
.
id
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
service
.
enableKey
).
toHaveBeenCalledWith
(
key
.
id
);
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
});
it
(
'
re-fetches deploy keys when disabling a key
'
,
(
done
)
=>
{
it
(
'
re-fetches deploy keys when disabling a key
'
,
done
=>
{
const
key
=
data
.
public_keys
[
0
];
spyOn
(
window
,
'
confirm
'
).
and
.
returnValue
(
true
);
spyOn
(
vm
.
service
,
'
getKeys
'
);
spyOn
(
vm
.
service
,
'
disableKey
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
)
=>
{
resolve
();
setTimeout
(()
=>
{
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
}));
spyOn
(
vm
.
service
,
'
disableKey
'
).
and
.
callFake
(()
=>
Promise
.
resolve
());
eventHub
.
$emit
(
'
disable.key
'
,
key
);
expect
(
vm
.
service
.
disableKey
).
toHaveBeenCalledWith
(
key
.
id
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
service
.
disableKey
).
toHaveBeenCalledWith
(
key
.
id
);
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
});
it
(
'
calls disableKey when removing a key
'
,
(
done
)
=>
{
it
(
'
calls disableKey when removing a key
'
,
done
=>
{
const
key
=
data
.
public_keys
[
0
];
spyOn
(
window
,
'
confirm
'
).
and
.
returnValue
(
true
);
spyOn
(
vm
.
service
,
'
getKeys
'
);
spyOn
(
vm
.
service
,
'
disableKey
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
)
=>
{
resolve
();
setTimeout
(()
=>
{
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
}));
spyOn
(
vm
.
service
,
'
disableKey
'
).
and
.
callFake
(()
=>
Promise
.
resolve
());
eventHub
.
$emit
(
'
remove.key
'
,
key
);
expect
(
vm
.
service
.
disableKey
).
toHaveBeenCalledWith
(
key
.
id
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
service
.
disableKey
).
toHaveBeenCalledWith
(
key
.
id
);
expect
(
vm
.
service
.
getKeys
).
toHaveBeenCalled
();
done
();
});
});
it
(
'
hasKeys returns true when there are keys
'
,
()
=>
{
expect
(
vm
.
hasKeys
).
toEqual
(
3
);
});
it
(
'
resets
remove button loading state
'
,
(
done
)
=>
{
it
(
'
resets
disable button loading state
'
,
done
=>
{
spyOn
(
window
,
'
confirm
'
).
and
.
returnValue
(
false
);
const
btn
=
vm
.
$el
.
querySelector
(
'
.btn-warning
'
);
...
...
@@ -149,7 +147,7 @@ describe('Deploy keys app component', () => {
btn
.
click
();
Vue
.
nextTick
(()
=>
{
expect
(
btn
.
querySelector
(
'
.
fa
'
)).
toBeNull
();
expect
(
btn
.
querySelector
(
'
.
btn-warning
'
)).
not
.
toExist
();
done
();
});
...
...
spec/javascripts/deploy_keys/components/key_spec.js
View file @
5e436de6
...
...
@@ -7,7 +7,7 @@ describe('Deploy keys key', () => {
let
vm
;
const
KeyComponent
=
Vue
.
extend
(
key
);
const
data
=
getJSONFixture
(
'
deploy_keys/keys.json
'
);
const
createComponent
=
(
deployKey
)
=>
{
const
createComponent
=
deployKey
=>
{
const
store
=
new
DeployKeysStore
();
store
.
keys
=
data
;
...
...
@@ -23,37 +23,42 @@ describe('Deploy keys key', () => {
describe
(
'
enabled key
'
,
()
=>
{
const
deployKey
=
data
.
enabled_keys
[
0
];
beforeEach
(
(
done
)
=>
{
beforeEach
(
done
=>
{
createComponent
(
deployKey
);
setTimeout
(
done
);
});
it
(
'
renders the keys title
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.title
'
).
textContent
.
trim
(),
).
toContain
(
'
My title
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.title
'
).
textContent
.
trim
()).
toContain
(
'
My title
'
);
});
it
(
'
renders human friendly formatted created date
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.key-created-at
'
).
textContent
.
trim
()
,
)
.
toBe
(
`created
${
getTimeago
().
format
(
deployKey
.
created_at
)}
`
)
;
expect
(
vm
.
$el
.
querySelector
(
'
.key-created-at
'
).
textContent
.
trim
()).
toBe
(
`
${
getTimeago
().
format
(
deployKey
.
created_at
)}
`
,
);
});
it
(
'
shows edit button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
0
].
textContent
.
trim
(),
).
toBe
(
'
Edit
'
);
it
(
'
shows pencil button for editing
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.btn .ic-pencil
'
)).
toExist
();
});
it
(
'
shows remove button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
1
].
textContent
.
trim
(),
).
toBe
(
'
Remove
'
);
it
(
'
shows disable button when the project is not deletable
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.btn .ic-cancel
'
)).
toExist
();
});
it
(
'
shows write access title when key has write access
'
,
(
done
)
=>
{
it
(
'
shows remove button when the project is deletable
'
,
done
=>
{
vm
.
deployKey
.
destroyed_when_orphaned
=
true
;
vm
.
deployKey
.
almost_orphaned
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.btn .ic-remove
'
)).
toExist
();
done
();
});
});
});
describe
(
'
deploy key labels
'
,
()
=>
{
it
(
'
shows write access title when key has write access
'
,
done
=>
{
vm
.
deployKey
.
deploy_keys_projects
[
0
].
can_push
=
true
;
Vue
.
nextTick
(()
=>
{
...
...
@@ -64,7 +69,7 @@ describe('Deploy keys key', () => {
});
});
it
(
'
does not show write access title when key has write access
'
,
(
done
)
=>
{
it
(
'
does not show write access title when key has write access
'
,
done
=>
{
vm
.
deployKey
.
deploy_keys_projects
[
0
].
can_push
=
false
;
Vue
.
nextTick
(()
=>
{
...
...
@@ -74,36 +79,73 @@ describe('Deploy keys key', () => {
done
();
});
});
it
(
'
shows expandable button if more than two projects
'
,
()
=>
{
const
labels
=
vm
.
$el
.
querySelectorAll
(
'
.deploy-project-label
'
);
expect
(
labels
.
length
).
toBe
(
2
);
expect
(
labels
[
1
].
textContent
).
toContain
(
'
others
'
);
expect
(
labels
[
1
].
getAttribute
(
'
data-original-title
'
)).
toContain
(
'
Expand
'
);
});
it
(
'
expands all project labels after click
'
,
done
=>
{
const
length
=
vm
.
deployKey
.
deploy_keys_projects
.
length
;
vm
.
$el
.
querySelectorAll
(
'
.deploy-project-label
'
)[
1
].
click
();
Vue
.
nextTick
(()
=>
{
const
labels
=
vm
.
$el
.
querySelectorAll
(
'
.deploy-project-label
'
);
expect
(
labels
.
length
).
toBe
(
length
);
expect
(
labels
[
1
].
textContent
).
not
.
toContain
(
`+
${
length
}
others`
);
expect
(
labels
[
1
].
getAttribute
(
'
data-original-title
'
)).
not
.
toContain
(
'
Expand
'
);
done
();
});
});
it
(
'
shows two projects
'
,
done
=>
{
vm
.
deployKey
.
deploy_keys_projects
=
[...
vm
.
deployKey
.
deploy_keys_projects
].
slice
(
0
,
2
);
Vue
.
nextTick
(()
=>
{
const
labels
=
vm
.
$el
.
querySelectorAll
(
'
.deploy-project-label
'
);
expect
(
labels
.
length
).
toBe
(
2
);
expect
(
labels
[
1
].
textContent
).
toContain
(
vm
.
deployKey
.
deploy_keys_projects
[
1
].
project
.
full_name
,
);
done
();
});
});
});
describe
(
'
public keys
'
,
()
=>
{
const
deployKey
=
data
.
public_keys
[
0
];
beforeEach
(
(
done
)
=>
{
beforeEach
(
done
=>
{
createComponent
(
deployKey
);
setTimeout
(
done
);
});
it
(
'
shows edit button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
0
].
textContent
.
trim
(),
).
toBe
(
'
Edit
'
);
it
(
'
renders deploy keys without any enabled projects
'
,
done
=>
{
vm
.
deployKey
.
deploy_keys_projects
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.deploy-project-list
'
).
textContent
.
trim
()).
toBe
(
'
None
'
);
done
();
});
});
it
(
'
shows enable button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
1
].
textContent
.
trim
(),
).
toBe
(
'
Enable
'
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
0
].
textContent
.
trim
()).
toBe
(
'
Enable
'
);
});
it
(
'
shows disable button when key is enabled
'
,
(
done
)
=>
{
it
(
'
shows pencil button for editing
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.btn .ic-pencil
'
)).
toExist
();
});
it
(
'
shows disable button when key is enabled
'
,
done
=>
{
vm
.
store
.
keys
.
enabled_keys
.
push
(
deployKey
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.btn
'
)[
1
].
textContent
.
trim
(),
).
toBe
(
'
Disable
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.btn .ic-cancel
'
)).
toExist
();
done
();
});
...
...
spec/javascripts/deploy_keys/components/keys_panel_spec.js
View file @
5e436de6
...
...
@@ -6,7 +6,7 @@ describe('Deploy keys panel', () => {
const
data
=
getJSONFixture
(
'
deploy_keys/keys.json
'
);
let
vm
;
beforeEach
(
(
done
)
=>
{
beforeEach
(
done
=>
{
const
DeployKeysPanelComponent
=
Vue
.
extend
(
deployKeysPanel
);
const
store
=
new
DeployKeysStore
();
store
.
keys
=
data
;
...
...
@@ -24,46 +24,38 @@ describe('Deploy keys panel', () => {
setTimeout
(
done
);
});
it
(
'
renders the title with keys count
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
h5
'
).
textContent
.
trim
(),
).
toContain
(
'
test
'
);
expect
(
vm
.
$el
.
querySelector
(
'
h5
'
).
textContent
.
trim
(),
).
toContain
(
`(
${
vm
.
keys
.
length
}
)`
);
it
(
'
renders list of keys
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.deploy-key
'
).
length
).
toBe
(
vm
.
keys
.
length
);
});
it
(
'
renders list of keys
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
li
'
).
length
,
).
toBe
(
vm
.
keys
.
length
);
it
(
'
renders table header
'
,
()
=>
{
const
tableHeader
=
vm
.
$el
.
querySelector
(
'
.table-row-header
'
);
expect
(
tableHeader
).
toExist
();
expect
(
tableHeader
.
textContent
).
toContain
(
'
Deploy key
'
);
expect
(
tableHeader
.
textContent
).
toContain
(
'
Project usage
'
);
expect
(
tableHeader
.
textContent
).
toContain
(
'
Created
'
);
});
it
(
'
renders help box if keys are empty
'
,
(
done
)
=>
{
it
(
'
renders help box if keys are empty
'
,
done
=>
{
vm
.
keys
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.settings-message
'
),
).
toBeDefined
();
expect
(
vm
.
$el
.
querySelector
(
'
.settings-message
'
)).
toBeDefined
();
expect
(
vm
.
$el
.
querySelector
(
'
.settings-message
'
).
textContent
.
trim
()
,
)
.
toBe
(
'
No deploy keys found. Create one with the form above.
'
)
;
expect
(
vm
.
$el
.
querySelector
(
'
.settings-message
'
).
textContent
.
trim
()).
toBe
(
'
No deploy keys found. Create one with the form above.
'
,
);
done
();
});
});
it
(
'
does not render help box if keys are empty & showHelpBox is false
'
,
(
done
)
=>
{
it
(
'
renders no table header if keys are empty
'
,
done
=>
{
vm
.
keys
=
[];
vm
.
showHelpBox
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.settings-message
'
),
).
toBeNull
();
expect
(
vm
.
$el
.
querySelector
(
'
.table-row-header
'
)).
not
.
toExist
();
done
();
});
...
...
spec/javascripts/fixtures/deploy_keys.rb
View file @
5e436de6
...
...
@@ -7,6 +7,8 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
let
(
:namespace
)
{
create
(
:namespace
,
name:
'frontend-fixtures'
)}
let
(
:project
)
{
create
(
:project_empty_repo
,
namespace:
namespace
,
path:
'todos-project'
)
}
let
(
:project2
)
{
create
(
:project
,
:internal
)}
let
(
:project3
)
{
create
(
:project
,
:internal
)}
let
(
:project4
)
{
create
(
:project
,
:internal
)}
before
(
:all
)
do
clean_frontend_fixtures
(
'deploy_keys/'
)
...
...
@@ -28,6 +30,8 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
internal_key
=
create
(
:deploy_key
,
key:
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com'
)
create
(
:deploy_keys_project
,
project:
project
,
deploy_key:
project_key
)
create
(
:deploy_keys_project
,
project:
project2
,
deploy_key:
internal_key
)
create
(
:deploy_keys_project
,
project:
project3
,
deploy_key:
project_key
)
create
(
:deploy_keys_project
,
project:
project4
,
deploy_key:
project_key
)
get
:index
,
namespace_id:
project
.
namespace
.
to_param
,
...
...
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