Merge branch '40533-groups-tree-updates' into 'master'
Groups tree enhancements Closes #40533 See merge request gitlab-org/gitlab-ce!15980
This commit is contained in:
commit
1e950e3148
|
@ -77,7 +77,8 @@ export default {
|
||||||
class="group-row"
|
class="group-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="group-row-contents">
|
class="group-row-contents"
|
||||||
|
:class="{ 'project-row-contents': !isGroup }">
|
||||||
<item-actions
|
<item-actions
|
||||||
v-if="isGroup"
|
v-if="isGroup"
|
||||||
:group="group"
|
:group="group"
|
||||||
|
@ -97,7 +98,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="avatar-container s40 hidden-xs"
|
class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs"
|
||||||
:class="{ 'content-loading': group.isChildrenLoading }"
|
:class="{ 'content-loading': group.isChildrenLoading }"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -106,11 +107,12 @@ export default {
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="hasAvatar"
|
v-if="hasAvatar"
|
||||||
class="avatar s40"
|
class="avatar s24"
|
||||||
:src="group.avatarUrl"
|
:src="group.avatarUrl"
|
||||||
/>
|
/>
|
||||||
<identicon
|
<identicon
|
||||||
v-else
|
v-else
|
||||||
|
size-class="s24"
|
||||||
:entity-id=group.id
|
:entity-id=group.id
|
||||||
:entity-name="group.name"
|
:entity-name="group.name"
|
||||||
/>
|
/>
|
||||||
|
@ -123,7 +125,7 @@ export default {
|
||||||
:href="group.relativePath"
|
:href="group.relativePath"
|
||||||
:title="group.fullName"
|
:title="group.fullName"
|
||||||
class="no-expand"
|
class="no-expand"
|
||||||
data-placement="top"
|
data-placement="bottom"
|
||||||
>{{
|
>{{
|
||||||
// ending bracket must be by closing tag to prevent
|
// ending bracket must be by closing tag to prevent
|
||||||
// link hover text-decoration from over-extending
|
// link hover text-decoration from over-extending
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { s__ } from '../../locale';
|
import { s__ } from '~/locale';
|
||||||
import tooltip from '../../vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
import modal from '../../vue_shared/components/modal.vue';
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import modal from '~/vue_shared/components/modal.vue';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
import { COMMON_STR } from '../constants';
|
import { COMMON_STR } from '../constants';
|
||||||
import Icon from '../../vue_shared/components/icon.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
icon,
|
||||||
modal,
|
modal,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
@ -64,10 +64,9 @@ export default {
|
||||||
:title="editBtnTitle"
|
:title="editBtnTitle"
|
||||||
:aria-label="editBtnTitle"
|
:aria-label="editBtnTitle"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
|
data-placement="bottom"
|
||||||
class="edit-group btn no-expand">
|
class="edit-group btn no-expand">
|
||||||
<icon
|
<icon name="settings"/>
|
||||||
name="settings">
|
|
||||||
</icon>
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-tooltip
|
v-tooltip
|
||||||
|
@ -77,10 +76,9 @@ export default {
|
||||||
:title="leaveBtnTitle"
|
:title="leaveBtnTitle"
|
||||||
:aria-label="leaveBtnTitle"
|
:aria-label="leaveBtnTitle"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
|
data-placement="bottom"
|
||||||
class="leave-group btn no-expand">
|
class="leave-group btn no-expand">
|
||||||
<i
|
<icon name="leave"/>
|
||||||
class="fa fa-sign-out"
|
|
||||||
aria-hidden="true"/>
|
|
||||||
</a>
|
</a>
|
||||||
<modal
|
<modal
|
||||||
v-show="modalStatus"
|
v-show="modalStatus"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
isGroupOpen: {
|
isGroupOpen: {
|
||||||
|
@ -7,9 +9,12 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
icon,
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return this.isGroupOpen ? 'fa-caret-down' : 'fa-caret-right';
|
return this.isGroupOpen ? 'angle-down' : 'angle-right';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -17,9 +22,9 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="folder-caret">
|
<span class="folder-caret">
|
||||||
<i
|
<icon
|
||||||
:class="iconClass"
|
:size="12"
|
||||||
class="fa"
|
:name="iconClass"
|
||||||
aria-hidden="true"/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import tooltip from '../../vue_shared/directives/tooltip';
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||||
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
|
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
|
||||||
|
import itemStatsValue from './item_stats_value.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
directives: {
|
components: {
|
||||||
tooltip,
|
icon,
|
||||||
|
timeAgoTooltip,
|
||||||
|
itemStatsValue,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
|
@ -34,65 +38,47 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<span
|
<item-stats-value
|
||||||
v-tooltip
|
|
||||||
v-if="isGroup"
|
v-if="isGroup"
|
||||||
|
css-class="number-subgroups"
|
||||||
|
icon-name="folder"
|
||||||
:title="s__('Subgroups')"
|
:title="s__('Subgroups')"
|
||||||
class="number-subgroups"
|
:value=item.subgroupCount
|
||||||
data-placement="top"
|
|
||||||
data-container="body">
|
|
||||||
<i
|
|
||||||
class="fa fa-folder"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
{{item.subgroupCount}}
|
<item-stats-value
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-tooltip
|
|
||||||
v-if="isGroup"
|
v-if="isGroup"
|
||||||
|
css-class="number-projects"
|
||||||
|
icon-name="bookmark"
|
||||||
:title="s__('Projects')"
|
:title="s__('Projects')"
|
||||||
class="number-projects"
|
:value=item.projectCount
|
||||||
data-placement="top"
|
|
||||||
data-container="body">
|
|
||||||
<i
|
|
||||||
class="fa fa-bookmark"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
{{item.projectCount}}
|
<item-stats-value
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-tooltip
|
|
||||||
v-if="isGroup"
|
v-if="isGroup"
|
||||||
|
css-class="number-users"
|
||||||
|
icon-name="users"
|
||||||
:title="s__('Members')"
|
:title="s__('Members')"
|
||||||
class="number-users"
|
:value=item.memberCount
|
||||||
data-placement="top"
|
|
||||||
data-container="body">
|
|
||||||
<i
|
|
||||||
class="fa fa-users"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
{{item.memberCount}}
|
<item-stats-value
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="isProject"
|
v-if="isProject"
|
||||||
class="project-stars">
|
css-class="project-stars"
|
||||||
<i
|
icon-name="star"
|
||||||
class="fa fa-star"
|
:value=item.starCount
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
{{item.starCount}}
|
<item-stats-value
|
||||||
</span>
|
css-class="item-visibility"
|
||||||
<span
|
tooltip-placement="left"
|
||||||
v-tooltip
|
:icon-name="visibilityIcon"
|
||||||
:title="visibilityTooltip"
|
:title="visibilityTooltip"
|
||||||
data-placement="left"
|
|
||||||
data-container="body"
|
|
||||||
class="item-visibility">
|
|
||||||
<i
|
|
||||||
:class="visibilityIcon"
|
|
||||||
class="fa"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
<div
|
||||||
|
class="last-updated"
|
||||||
|
v-if="isProject"
|
||||||
|
>
|
||||||
|
<time-ago-tooltip
|
||||||
|
tooltip-placement="bottom"
|
||||||
|
:time="item.updatedAt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script>
|
||||||
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
cssClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
iconName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
tooltipPlacement: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'bottom',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* value could either be number or string
|
||||||
|
* as `memberCount` is always passed as string
|
||||||
|
* while `subgroupCount` & `projectCount`
|
||||||
|
* are always number
|
||||||
|
*/
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isValuePresent() {
|
||||||
|
return this.value !== '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-tooltip
|
||||||
|
data-container="body"
|
||||||
|
:data-placement="tooltipPlacement"
|
||||||
|
:class="cssClass"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<icon :name="iconName"/>
|
||||||
|
<span
|
||||||
|
v-if="isValuePresent"
|
||||||
|
class="stat-value"
|
||||||
|
>
|
||||||
|
{{value}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
|
@ -1,7 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
import { ITEM_TYPE } from '../constants';
|
import { ITEM_TYPE } from '../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
icon,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
itemType: {
|
itemType: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -16,9 +20,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
iconClass() {
|
iconClass() {
|
||||||
if (this.itemType === ITEM_TYPE.GROUP) {
|
if (this.itemType === ITEM_TYPE.GROUP) {
|
||||||
return this.isGroupOpen ? 'fa-folder-open' : 'fa-folder';
|
return this.isGroupOpen ? 'folder-open' : 'folder';
|
||||||
}
|
}
|
||||||
return 'fa-bookmark';
|
return 'bookmark';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -26,9 +30,6 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="item-type-icon">
|
<span class="item-type-icon">
|
||||||
<i
|
<icon :name="iconClass"/>
|
||||||
:class="iconClass"
|
|
||||||
class="fa"
|
|
||||||
aria-hidden="true"/>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VISIBILITY_TYPE_ICON = {
|
export const VISIBILITY_TYPE_ICON = {
|
||||||
public: 'fa-globe',
|
public: 'earth',
|
||||||
internal: 'fa-shield',
|
internal: 'shield',
|
||||||
private: 'fa-lock',
|
private: 'lock',
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,6 +91,7 @@ export default class GroupsStore {
|
||||||
subgroupCount: rawGroupItem.subgroup_count,
|
subgroupCount: rawGroupItem.subgroup_count,
|
||||||
memberCount: rawGroupItem.number_users_with_delimiter,
|
memberCount: rawGroupItem.number_users_with_delimiter,
|
||||||
starCount: rawGroupItem.star_count,
|
starCount: rawGroupItem.star_count,
|
||||||
|
updatedAt: rawGroupItem.updated_at,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
&.s16 { font-size: 12px; line-height: 1.33; }
|
&.s16 { font-size: 12px; line-height: 1.33; }
|
||||||
&.s24 { font-size: 14px; line-height: 1.8; }
|
&.s24 { font-size: 13px; line-height: 1.8; }
|
||||||
&.s26 { font-size: 20px; line-height: 1.33; }
|
&.s26 { font-size: 20px; line-height: 1.33; }
|
||||||
&.s32 { font-size: 20px; line-height: 30px; }
|
&.s32 { font-size: 20px; line-height: 30px; }
|
||||||
&.s40 { font-size: 16px; line-height: 38px; }
|
&.s40 { font-size: 16px; line-height: 38px; }
|
||||||
|
|
|
@ -126,10 +126,8 @@ ul.content-list {
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
p {
|
|
||||||
@include str-truncated;
|
@include str-truncated;
|
||||||
margin-bottom: 0;
|
color: $gl-text-color-secondary;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
@ -315,7 +313,7 @@ ul.indent-list {
|
||||||
border: 2px solid $white-normal;
|
border: 2px solid $white-normal;
|
||||||
|
|
||||||
&.identicon {
|
&.identicon {
|
||||||
line-height: 30px;
|
line-height: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,14 +347,19 @@ ul.indent-list {
|
||||||
|
|
||||||
.folder-caret {
|
.folder-caret {
|
||||||
width: 15px;
|
width: 15px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-type-icon {
|
.item-type-icon {
|
||||||
|
margin-top: 2px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .group-row:not(.has-children) {
|
> .group-row:not(.has-children) {
|
||||||
.folder-caret .fa {
|
.folder-caret {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,12 +442,61 @@ ul.indent-list {
|
||||||
|
|
||||||
.avatar-container > a {
|
.avatar-container > a {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-more-items {
|
&.has-more-items {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
position: relative;
|
||||||
|
line-height: 46px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 16px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
margin: 2px 0 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-left: 5px;
|
||||||
|
|
||||||
|
> .btn {
|
||||||
|
margin-right: $btn-xs-side-margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-row-contents .stats {
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
> span:first-child {
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-visibility {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-updated {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
min-width: 250px;
|
||||||
|
text-align: right;
|
||||||
|
color: $gl-text-color-secondary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,12 +508,12 @@ ul.indent-list {
|
||||||
|
|
||||||
ul.group-list-tree {
|
ul.group-list-tree {
|
||||||
li.group-row {
|
li.group-row {
|
||||||
&.has-description .title {
|
> .group-row-contents .title {
|
||||||
line-height: inherit;
|
line-height: $list-text-height;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.has-description) .title {
|
&.has-description > .group-row-contents .title {
|
||||||
line-height: $list-text-height;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: Update groups tree to use GitLab SVG icons, add last updated at information
|
||||||
|
for projects
|
||||||
|
merge_request: 15980
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -94,22 +94,14 @@ feature 'Dashboard Groups page', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'can toggle parent group' do
|
it 'can toggle parent group' do
|
||||||
# Collapsed by default
|
|
||||||
expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1)
|
|
||||||
expect(page).to have_selector("#group-#{group.id} .fa-caret-right")
|
|
||||||
|
|
||||||
# expand
|
# expand
|
||||||
click_group_caret(group)
|
click_group_caret(group)
|
||||||
|
|
||||||
expect(page).to have_selector("#group-#{group.id} .fa-caret-down")
|
|
||||||
expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right", count: 1)
|
|
||||||
expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}")
|
expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}")
|
||||||
|
|
||||||
# collapse
|
# collapse
|
||||||
click_group_caret(group)
|
click_group_caret(group)
|
||||||
|
|
||||||
expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1)
|
|
||||||
expect(page).to have_selector("#group-#{group.id} .fa-caret-right")
|
|
||||||
expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}")
|
expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,24 +16,20 @@ describe('ItemCaretComponent', () => {
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('should render component template correctly', () => {
|
it('should render component template correctly', () => {
|
||||||
const vm = createComponent();
|
const vm = createComponent();
|
||||||
vm.$mount();
|
|
||||||
expect(vm.$el.classList.contains('folder-caret')).toBeTruthy();
|
expect(vm.$el.classList.contains('folder-caret')).toBeTruthy();
|
||||||
|
expect(vm.$el.querySelectorAll('svg').length).toBe(1);
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render caret down icon if `isGroupOpen` prop is `true`', () => {
|
it('should render caret down icon if `isGroupOpen` prop is `true`', () => {
|
||||||
const vm = createComponent(true);
|
const vm = createComponent(true);
|
||||||
vm.$mount();
|
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-down');
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(1);
|
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(0);
|
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render caret right icon if `isGroupOpen` prop is `false`', () => {
|
it('should render caret right icon if `isGroupOpen` prop is `false`', () => {
|
||||||
const vm = createComponent();
|
const vm = createComponent();
|
||||||
vm.$mount();
|
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-right');
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(0);
|
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(1);
|
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,6 @@ describe('ItemStatsComponent', () => {
|
||||||
Object.keys(VISIBILITY_TYPE_ICON).forEach((visibility) => {
|
Object.keys(VISIBILITY_TYPE_ICON).forEach((visibility) => {
|
||||||
const item = Object.assign({}, mockParentGroupItem, { visibility });
|
const item = Object.assign({}, mockParentGroupItem, { visibility });
|
||||||
const vm = createComponent(item);
|
const vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.visibilityIcon).toBe(VISIBILITY_TYPE_ICON[visibility]);
|
expect(vm.visibilityIcon).toBe(VISIBILITY_TYPE_ICON[visibility]);
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
@ -41,7 +40,6 @@ describe('ItemStatsComponent', () => {
|
||||||
type: ITEM_TYPE.GROUP,
|
type: ITEM_TYPE.GROUP,
|
||||||
});
|
});
|
||||||
const vm = createComponent(item);
|
const vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.visibilityTooltip).toBe(GROUP_VISIBILITY_TYPE[visibility]);
|
expect(vm.visibilityTooltip).toBe(GROUP_VISIBILITY_TYPE[visibility]);
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
@ -54,7 +52,6 @@ describe('ItemStatsComponent', () => {
|
||||||
type: ITEM_TYPE.PROJECT,
|
type: ITEM_TYPE.PROJECT,
|
||||||
});
|
});
|
||||||
const vm = createComponent(item);
|
const vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.visibilityTooltip).toBe(PROJECT_VISIBILITY_TYPE[visibility]);
|
expect(vm.visibilityTooltip).toBe(PROJECT_VISIBILITY_TYPE[visibility]);
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
@ -68,13 +65,11 @@ describe('ItemStatsComponent', () => {
|
||||||
|
|
||||||
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT });
|
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT });
|
||||||
vm = createComponent(item);
|
vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.isProject).toBeTruthy();
|
expect(vm.isProject).toBeTruthy();
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
|
|
||||||
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP });
|
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP });
|
||||||
vm = createComponent(item);
|
vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.isProject).toBeFalsy();
|
expect(vm.isProject).toBeFalsy();
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
@ -87,13 +82,11 @@ describe('ItemStatsComponent', () => {
|
||||||
|
|
||||||
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP });
|
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP });
|
||||||
vm = createComponent(item);
|
vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.isGroup).toBeTruthy();
|
expect(vm.isGroup).toBeTruthy();
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
|
|
||||||
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT });
|
item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT });
|
||||||
vm = createComponent(item);
|
vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
expect(vm.isGroup).toBeFalsy();
|
expect(vm.isGroup).toBeFalsy();
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
@ -101,57 +94,37 @@ describe('ItemStatsComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('should render component template correctly', () => {
|
it('renders component container element correctly', () => {
|
||||||
|
const vm = createComponent();
|
||||||
|
|
||||||
|
expect(vm.$el.classList.contains('stats')).toBeTruthy();
|
||||||
|
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders item visibility icon and tooltip correctly', () => {
|
||||||
const vm = createComponent();
|
const vm = createComponent();
|
||||||
vm.$mount();
|
|
||||||
|
|
||||||
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
|
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
|
||||||
expect(vm.$el.classList.contains('.stats')).toBeDefined();
|
expect(visibilityIconEl).not.toBe(null);
|
||||||
expect(visibilityIconEl).toBeDefined();
|
|
||||||
expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
|
expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
|
||||||
expect(visibilityIconEl.querySelector('i.fa')).toBeDefined();
|
expect(visibilityIconEl.querySelectorAll('svg').length > 0).toBeTruthy();
|
||||||
|
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render stat icons if `item.type` is Group', () => {
|
it('renders start count and last updated information for project item correctly', () => {
|
||||||
const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP });
|
|
||||||
const vm = createComponent(item);
|
|
||||||
vm.$mount();
|
|
||||||
|
|
||||||
const subgroupIconEl = vm.$el.querySelector('span.number-subgroups');
|
|
||||||
expect(subgroupIconEl).toBeDefined();
|
|
||||||
expect(subgroupIconEl.dataset.originalTitle).toBe('Subgroups');
|
|
||||||
expect(subgroupIconEl.querySelector('i.fa.fa-folder')).toBeDefined();
|
|
||||||
expect(subgroupIconEl.innerText.trim()).toBe(`${vm.item.subgroupCount}`);
|
|
||||||
|
|
||||||
const projectsIconEl = vm.$el.querySelector('span.number-projects');
|
|
||||||
expect(projectsIconEl).toBeDefined();
|
|
||||||
expect(projectsIconEl.dataset.originalTitle).toBe('Projects');
|
|
||||||
expect(projectsIconEl.querySelector('i.fa.fa-bookmark')).toBeDefined();
|
|
||||||
expect(projectsIconEl.innerText.trim()).toBe(`${vm.item.projectCount}`);
|
|
||||||
|
|
||||||
const membersIconEl = vm.$el.querySelector('span.number-users');
|
|
||||||
expect(membersIconEl).toBeDefined();
|
|
||||||
expect(membersIconEl.dataset.originalTitle).toBe('Members');
|
|
||||||
expect(membersIconEl.querySelector('i.fa.fa-users')).toBeDefined();
|
|
||||||
expect(membersIconEl.innerText.trim()).toBe(`${vm.item.memberCount}`);
|
|
||||||
|
|
||||||
vm.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render stat icons if `item.type` is Project', () => {
|
|
||||||
const item = Object.assign({}, mockParentGroupItem, {
|
const item = Object.assign({}, mockParentGroupItem, {
|
||||||
type: ITEM_TYPE.PROJECT,
|
type: ITEM_TYPE.PROJECT,
|
||||||
starCount: 4,
|
starCount: 4,
|
||||||
});
|
});
|
||||||
const vm = createComponent(item);
|
const vm = createComponent(item);
|
||||||
vm.$mount();
|
|
||||||
|
|
||||||
const projectStarIconEl = vm.$el.querySelector('.project-stars');
|
const projectStarIconEl = vm.$el.querySelector('.project-stars');
|
||||||
expect(projectStarIconEl).toBeDefined();
|
expect(projectStarIconEl).not.toBe(null);
|
||||||
expect(projectStarIconEl.querySelector('i.fa.fa-star')).toBeDefined();
|
expect(projectStarIconEl.querySelectorAll('svg').length > 0).toBeTruthy();
|
||||||
expect(projectStarIconEl.innerText.trim()).toBe(`${vm.item.starCount}`);
|
expect(projectStarIconEl.querySelectorAll('.stat-value').length > 0).toBeTruthy();
|
||||||
|
expect(vm.$el.querySelectorAll('.last-updated').length > 0).toBeTruthy();
|
||||||
|
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import itemStatsValueComponent from '~/groups/components/item_stats_value.vue';
|
||||||
|
|
||||||
|
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||||
|
|
||||||
|
const createComponent = ({ title, cssClass, iconName, tooltipPlacement, value }) => {
|
||||||
|
const Component = Vue.extend(itemStatsValueComponent);
|
||||||
|
|
||||||
|
return mountComponent(Component, {
|
||||||
|
title,
|
||||||
|
cssClass,
|
||||||
|
iconName,
|
||||||
|
tooltipPlacement,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ItemStatsValueComponent', () => {
|
||||||
|
describe('computed', () => {
|
||||||
|
let vm;
|
||||||
|
const itemConfig = {
|
||||||
|
title: 'Subgroups',
|
||||||
|
cssClass: 'number-subgroups',
|
||||||
|
iconName: 'folder',
|
||||||
|
tooltipPlacement: 'left',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('isValuePresent', () => {
|
||||||
|
it('returns true if non-empty `value` is present', () => {
|
||||||
|
vm = createComponent(Object.assign({}, itemConfig, { value: 10 }));
|
||||||
|
expect(vm.isValuePresent).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if empty `value` is present', () => {
|
||||||
|
vm = createComponent(itemConfig);
|
||||||
|
expect(vm.isValuePresent).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
let vm;
|
||||||
|
beforeEach(() => {
|
||||||
|
vm = createComponent({
|
||||||
|
title: 'Subgroups',
|
||||||
|
cssClass: 'number-subgroups',
|
||||||
|
iconName: 'folder',
|
||||||
|
tooltipPlacement: 'left',
|
||||||
|
value: 10,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders component element correctly', () => {
|
||||||
|
expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy();
|
||||||
|
expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy();
|
||||||
|
expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders element tooltip correctly', () => {
|
||||||
|
expect(vm.$el.dataset.originalTitle).toBe('Subgroups');
|
||||||
|
expect(vm.$el.dataset.placement).toBe('left');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders element icon correctly', () => {
|
||||||
|
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('folder');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders value count correctly', () => {
|
||||||
|
expect(vm.$el.querySelector('.stat-value').innerText.trim()).toContain('10');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -28,12 +28,12 @@ describe('ItemTypeIconComponent', () => {
|
||||||
|
|
||||||
vm = createComponent(ITEM_TYPE.GROUP, true);
|
vm = createComponent(ITEM_TYPE.GROUP, true);
|
||||||
vm.$mount();
|
vm.$mount();
|
||||||
expect(vm.$el.querySelector('i.fa.fa-folder-open')).toBeDefined();
|
expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder-open');
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
|
|
||||||
vm = createComponent(ITEM_TYPE.GROUP);
|
vm = createComponent(ITEM_TYPE.GROUP);
|
||||||
vm.$mount();
|
vm.$mount();
|
||||||
expect(vm.$el.querySelector('i.fa.fa-folder')).toBeDefined();
|
expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder');
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,12 +42,12 @@ describe('ItemTypeIconComponent', () => {
|
||||||
|
|
||||||
vm = createComponent(ITEM_TYPE.PROJECT);
|
vm = createComponent(ITEM_TYPE.PROJECT);
|
||||||
vm.$mount();
|
vm.$mount();
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(1);
|
expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('bookmark');
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
|
|
||||||
vm = createComponent(ITEM_TYPE.GROUP);
|
vm = createComponent(ITEM_TYPE.GROUP);
|
||||||
vm.$mount();
|
vm.$mount();
|
||||||
expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(0);
|
expect(vm.$el.querySelector('use').getAttribute('xlink:href')).not.toContain('bookmark');
|
||||||
vm.$destroy();
|
vm.$destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,9 +18,9 @@ export const PROJECT_VISIBILITY_TYPE = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VISIBILITY_TYPE_ICON = {
|
export const VISIBILITY_TYPE_ICON = {
|
||||||
public: 'fa-globe',
|
public: 'earth',
|
||||||
internal: 'fa-shield',
|
internal: 'shield',
|
||||||
private: 'fa-lock',
|
private: 'lock',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockParentGroupItem = {
|
export const mockParentGroupItem = {
|
||||||
|
@ -46,6 +46,7 @@ export const mockParentGroupItem = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
isChildrenLoading: false,
|
isChildrenLoading: false,
|
||||||
isBeingRemoved: false,
|
isBeingRemoved: false,
|
||||||
|
updatedAt: '2017-04-09T18:40:39.101Z',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockRawChildren = [
|
export const mockRawChildren = [
|
||||||
|
@ -69,6 +70,7 @@ export const mockRawChildren = [
|
||||||
subgroup_count: 2,
|
subgroup_count: 2,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
children: [],
|
children: [],
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -96,6 +98,7 @@ export const mockChildren = [
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
isChildrenLoading: false,
|
isChildrenLoading: false,
|
||||||
isBeingRemoved: false,
|
isBeingRemoved: false,
|
||||||
|
updatedAt: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -119,6 +122,7 @@ export const mockGroups = [
|
||||||
project_count: 2,
|
project_count: 2,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 67,
|
id: 67,
|
||||||
|
@ -139,6 +143,7 @@ export const mockGroups = [
|
||||||
project_count: 0,
|
project_count: 0,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 54,
|
id: 54,
|
||||||
|
@ -159,6 +164,7 @@ export const mockGroups = [
|
||||||
project_count: 0,
|
project_count: 0,
|
||||||
subgroup_count: 1,
|
subgroup_count: 1,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
|
@ -179,6 +185,7 @@ export const mockGroups = [
|
||||||
project_count: 1,
|
project_count: 1,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
|
@ -199,6 +206,7 @@ export const mockGroups = [
|
||||||
project_count: 2,
|
project_count: 2,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
|
@ -219,6 +227,7 @@ export const mockGroups = [
|
||||||
project_count: 1,
|
project_count: 1,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
@ -239,6 +248,7 @@ export const mockGroups = [
|
||||||
project_count: 4,
|
project_count: 4,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -262,6 +272,7 @@ export const mockSearchedGroups = [
|
||||||
project_count: 1,
|
project_count: 1,
|
||||||
subgroup_count: 2,
|
subgroup_count: 2,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: 57,
|
id: 57,
|
||||||
|
@ -282,6 +293,7 @@ export const mockSearchedGroups = [
|
||||||
project_count: 4,
|
project_count: 4,
|
||||||
subgroup_count: 2,
|
subgroup_count: 2,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: 60,
|
id: 60,
|
||||||
|
@ -302,6 +314,7 @@ export const mockSearchedGroups = [
|
||||||
project_count: 0,
|
project_count: 0,
|
||||||
subgroup_count: 1,
|
subgroup_count: 1,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: 61,
|
id: 61,
|
||||||
|
@ -322,6 +335,7 @@ export const mockSearchedGroups = [
|
||||||
project_count: 2,
|
project_count: 2,
|
||||||
subgroup_count: 0,
|
subgroup_count: 0,
|
||||||
can_leave: false,
|
can_leave: false,
|
||||||
|
updated_at: '2017-04-09T18:40:39.101Z',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: 17,
|
id: 17,
|
||||||
|
@ -336,6 +350,7 @@ export const mockSearchedGroups = [
|
||||||
permission: null,
|
permission: null,
|
||||||
edit_path: '/platform/hardware/bsp/kernel/common/v4.4/edit',
|
edit_path: '/platform/hardware/bsp/kernel/common/v4.4/edit',
|
||||||
star_count: 0,
|
star_count: 0,
|
||||||
|
updated_at: '2017-09-12T06:37:04.925Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 16,
|
id: 16,
|
||||||
|
@ -350,6 +365,7 @@ export const mockSearchedGroups = [
|
||||||
permission: null,
|
permission: null,
|
||||||
edit_path: '/platform/hardware/bsp/kernel/common/v4.1/edit',
|
edit_path: '/platform/hardware/bsp/kernel/common/v4.1/edit',
|
||||||
star_count: 0,
|
star_count: 0,
|
||||||
|
updated_at: '2017-04-09T18:41:03.112Z',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue