Merge branch '35408-group-auto-avatars' into 'master'
Show auto-generated avatars in Groups dashboard tree for Groups without avatars See merge request !13188
This commit is contained in:
commit
489a954942
7 changed files with 141 additions and 2 deletions
45
app/assets/javascripts/groups/components/group_identicon.vue
Normal file
45
app/assets/javascripts/groups/components/group_identicon.vue
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
entityId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
entityName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* This method is based on app/helpers/application_helper.rb#project_identicon
|
||||||
|
*/
|
||||||
|
identiconStyles() {
|
||||||
|
const allowedColors = [
|
||||||
|
'#FFEBEE',
|
||||||
|
'#F3E5F5',
|
||||||
|
'#E8EAF6',
|
||||||
|
'#E3F2FD',
|
||||||
|
'#E0F2F1',
|
||||||
|
'#FBE9E7',
|
||||||
|
'#EEEEEE',
|
||||||
|
];
|
||||||
|
|
||||||
|
const backgroundColor = allowedColors[this.entityId % 7];
|
||||||
|
|
||||||
|
return `background-color: ${backgroundColor}; color: #555;`;
|
||||||
|
},
|
||||||
|
identiconTitle() {
|
||||||
|
return this.entityName.charAt(0).toUpperCase();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="avatar s40 identicon"
|
||||||
|
:style="identiconStyles">
|
||||||
|
{{identiconTitle}}
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,7 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
import groupIdenticon from './group_identicon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
groupIdenticon,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
group: {
|
group: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -92,6 +96,9 @@ export default {
|
||||||
hasGroups() {
|
hasGroups() {
|
||||||
return Object.keys(this.group.subGroups).length > 0;
|
return Object.keys(this.group.subGroups).length > 0;
|
||||||
},
|
},
|
||||||
|
hasAvatar() {
|
||||||
|
return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -194,9 +201,15 @@ export default {
|
||||||
<a
|
<a
|
||||||
:href="group.groupPath">
|
:href="group.groupPath">
|
||||||
<img
|
<img
|
||||||
|
v-if="hasAvatar"
|
||||||
class="avatar s40"
|
class="avatar s40"
|
||||||
:src="group.avatarUrl"
|
:src="group.avatarUrl"
|
||||||
/>
|
/>
|
||||||
|
<group-identicon
|
||||||
|
v-else
|
||||||
|
:entity-id=group.id
|
||||||
|
:entity-name="group.name"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -369,6 +369,10 @@ ul.indent-list {
|
||||||
background-color: $row-hover;
|
background-color: $row-hover;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-container > a {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
changelogs/unreleased/35408-group-auto-avatars.yml
Normal file
4
changelogs/unreleased/35408-group-auto-avatars.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Show auto-generated avatars for Groups without avatars
|
||||||
|
merge_request: 13188
|
||||||
|
author:
|
60
spec/javascripts/groups/group_identicon_spec.js
Normal file
60
spec/javascripts/groups/group_identicon_spec.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import groupIdenticonComponent from '~/groups/components/group_identicon.vue';
|
||||||
|
import GroupsStore from '~/groups/stores/groups_store';
|
||||||
|
import { group1 } from './mock_data';
|
||||||
|
|
||||||
|
const createComponent = () => {
|
||||||
|
const Component = Vue.extend(groupIdenticonComponent);
|
||||||
|
const store = new GroupsStore();
|
||||||
|
const group = store.decorateGroup(group1);
|
||||||
|
|
||||||
|
return new Component({
|
||||||
|
propsData: {
|
||||||
|
entityId: group.id,
|
||||||
|
entityName: group.name,
|
||||||
|
},
|
||||||
|
}).$mount();
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('GroupIdenticonComponent', () => {
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vm = createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computed', () => {
|
||||||
|
describe('identiconStyles', () => {
|
||||||
|
it('should return styles attribute value with `background-color` property', () => {
|
||||||
|
vm.entityId = 4;
|
||||||
|
|
||||||
|
expect(vm.identiconStyles).toBeDefined();
|
||||||
|
expect(vm.identiconStyles.indexOf('background-color: #E0F2F1;') > -1).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return styles attribute value with `color` property', () => {
|
||||||
|
vm.entityId = 4;
|
||||||
|
|
||||||
|
expect(vm.identiconStyles).toBeDefined();
|
||||||
|
expect(vm.identiconStyles.indexOf('color: #555;') > -1).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('identiconTitle', () => {
|
||||||
|
it('should return first letter of entity title in uppercase', () => {
|
||||||
|
vm.entityName = 'dummy-group';
|
||||||
|
|
||||||
|
expect(vm.identiconTitle).toBeDefined();
|
||||||
|
expect(vm.identiconTitle).toBe('D');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('should render identicon', () => {
|
||||||
|
expect(vm.$el.nodeName).toBe('DIV');
|
||||||
|
expect(vm.$el.classList.contains('identicon')).toBeTruthy();
|
||||||
|
expect(vm.$el.getAttribute('style').indexOf('background-color') > -1).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -64,6 +64,19 @@ describe('Groups Component', () => {
|
||||||
expect(lists[2].querySelector('#group-1120').textContent).toContain(groups.id1119.subGroups.id1120.name);
|
expect(lists[2].querySelector('#group-1120').textContent).toContain(groups.id1119.subGroups.id1120.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render group identicon when group avatar is not present', () => {
|
||||||
|
const avatar = component.$el.querySelector('#group-12 .avatar-container .avatar');
|
||||||
|
expect(avatar.nodeName).toBe('DIV');
|
||||||
|
expect(avatar.classList.contains('identicon')).toBeTruthy();
|
||||||
|
expect(avatar.getAttribute('style').indexOf('background-color') > -1).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render group avatar when group avatar is present', () => {
|
||||||
|
const avatar = component.$el.querySelector('#group-1120 .avatar-container .avatar');
|
||||||
|
expect(avatar.nodeName).toBe('IMG');
|
||||||
|
expect(avatar.classList.contains('identicon')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should remove prefix of parent group', () => {
|
it('should remove prefix of parent group', () => {
|
||||||
expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4');
|
expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const group1 = {
|
const group1 = {
|
||||||
id: '12',
|
id: 12,
|
||||||
name: 'level1',
|
name: 'level1',
|
||||||
path: 'level1',
|
path: 'level1',
|
||||||
description: 'foo',
|
description: 'foo',
|
||||||
|
@ -71,7 +71,7 @@ const group21 = {
|
||||||
path: 'chef',
|
path: 'chef',
|
||||||
description: 'foo',
|
description: 'foo',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
avatar_url: null,
|
avatar_url: '/uploads/-/system/group/avatar/2/GitLab.png',
|
||||||
web_url: 'http://localhost:3000/groups/devops/chef',
|
web_url: 'http://localhost:3000/groups/devops/chef',
|
||||||
group_path: '/devops/chef',
|
group_path: '/devops/chef',
|
||||||
full_name: 'devops / chef',
|
full_name: 'devops / chef',
|
||||||
|
|
Loading…
Reference in a new issue