Commit f5dfd988 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Address feedback

parent 494e8090
...@@ -8,6 +8,7 @@ export default { ...@@ -8,6 +8,7 @@ export default {
baseGroup: { baseGroup: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}),
}, },
}, },
}; };
...@@ -15,6 +16,12 @@ export default { ...@@ -15,6 +16,12 @@ export default {
<template> <template>
<ul class="content-list group-list-tree"> <ul class="content-list group-list-tree">
<group-item v-for="(group, index) in groups" :key="index" :group="group" :base-group="baseGroup" :collection="groups" /> <group-item
v-for="(group, index) in groups"
:key="index"
:group="group"
:base-group="baseGroup"
:collection="groups"
/>
</ul> </ul>
</template> </template>
...@@ -102,71 +102,102 @@ export default { ...@@ -102,71 +102,102 @@ export default {
:id="groupDomId" :id="groupDomId"
:class="rowClass" :class="rowClass"
> >
<div class="group-row-contents"> <div
<div class="controls"> class="group-row-contents">
<div
class="controls">
<a <a
v-if="group.canEdit" v-if="group.canEdit"
class="edit-group btn" class="edit-group btn"
:href="group.editPath"> :href="group.editPath">
<i aria-hidden="true" class="fa fa-cogs"></i> <i aria-hidden="true" class="fa fa-cogs"></i>
</a> </a>
<a @click="onLeaveGroup" <a
@click="onLeaveGroup"
:href="group.leavePath" :href="group.leavePath"
class="leave-group btn" class="leave-group btn"
title="Leave this group"> title="Leave this group">
<i aria-hidden="true" class="fa fa-sign-out"></i> <i
aria-hidden="true"
class="fa fa-sign-out">
</i>
</a> </a>
</div> </div>
<div
<div class="stats"> class="stats">
<span class="number-projects"> <span
<i aria-hidden="true" class="fa fa-bookmark"></i> class="number-projects">
<i
aria-hidden="true"
class="fa fa-bookmark">
</i>
{{group.numberProjects}} {{group.numberProjects}}
</span> </span>
<span class="number-users"> <span
<i aria-hidden="true" class="fa fa-users"></i> class="number-users">
<i
aria-hidden="true"
class="fa fa-users">
</i>
{{group.numberUsers}} {{group.numberUsers}}
</span> </span>
<span class="group-visibility"> <span
<i aria-hidden="true" :class="visibilityIcon"></i> class="group-visibility">
<i
aria-hidden="true"
:class="visibilityIcon">
</i>
</span> </span>
</div> </div>
<div
<div class="folder-toggle-wrap"> class="folder-toggle-wrap">
<span class="folder-caret" v-if="group.hasSubgroups"> <span
class="folder-caret"
v-if="group.hasSubgroups">
<i <i
v-if="group.isOpen" v-if="group.isOpen"
class="fa fa-caret-down" /> class="fa fa-caret-down">
</i>
<i <i
v-if="!group.isOpen" v-if="!group.isOpen"
class="fa fa-caret-right" /> class="fa fa-caret-right">
</i>
</span> </span>
<span class="folder-icon"> <span class="folder-icon">
<i <i
v-if="group.isOpen" v-if="group.isOpen"
class="fa fa-folder-open" class="fa fa-folder-open"
aria-hidden="true"></i> aria-hidden="true">
</i>
<i <i
v-if="!group.isOpen" v-if="!group.isOpen"
class="fa fa-folder"></i> class="fa fa-folder"
aria-hidden="true">
</i>
</span> </span>
</div> </div>
<div
<div class="avatar-container s40 hidden-xs"> class="avatar-container s40 hidden-xs">
<a :href="group.webUrl"> <a
<img class="avatar s40" :src="group.avatarUrl" /> :href="group.webUrl">
<img
class="avatar s40"
:src="group.avatarUrl"
/>
</a> </a>
</div> </div>
<div
<div class="title"> class="title">
<a :href="group.webUrl">{{fullPath}}</a> <a
</div> :href="group.webUrl">{{fullPath}}</a>
<div class="description">
{{group.description}}
</div> </div>
<div
class="description">{{group.description}}</div>
</div> </div>
<group-folder v-if="group.isOpen && hasGroups" :groups="group.subGroups" :baseGroup="group" /> <group-folder
v-if="group.isOpen && hasGroups"
:groups="group.subGroups"
:baseGroup="group"
/>
</li> </li>
</template> </template>
<script> <script>
import TablePaginationComponent from '~/vue_shared/components/table_pagination.vue'; import TablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: {
'gl-pagination': TablePaginationComponent,
},
props: { props: {
groups: { groups: {
type: Object, type: Object,
...@@ -16,6 +13,9 @@ export default { ...@@ -16,6 +13,9 @@ export default {
required: true, required: true,
}, },
}, },
components: {
TablePagination,
},
methods: { methods: {
change(page) { change(page) {
const filterGroupsParam = gl.utils.getParameterByName('filter_groups'); const filterGroupsParam = gl.utils.getParameterByName('filter_groups');
...@@ -28,9 +28,12 @@ export default { ...@@ -28,9 +28,12 @@ export default {
<template> <template>
<div class="groups-list-tree-container"> <div class="groups-list-tree-container">
<group-folder :groups="groups" /> <group-folder
<gl-pagination :groups="groups"
/>
<table-pagination
:change="change" :change="change"
:pageInfo="pageInfo" /> :pageInfo="pageInfo"
/>
</div> </div>
</template> </template>
...@@ -10,7 +10,7 @@ import GroupsService from './services/groups_service'; ...@@ -10,7 +10,7 @@ import GroupsService from './services/groups_service';
import eventHub from './event_hub'; import eventHub from './event_hub';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('#dashboard-group-app'); const el = document.getElementById('dashboard-group-app');
// Don't do anything if element doesn't exist (No groups) // Don't do anything if element doesn't exist (No groups)
// This is for when the user enters directly to the page via URL // This is for when the user enters directly to the page via URL
...@@ -74,27 +74,28 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -74,27 +74,28 @@ document.addEventListener('DOMContentLoaded', () => {
} }
getGroups = this.service.getGroups(parentId, page, filterGroups, sort); getGroups = this.service.getGroups(parentId, page, filterGroups, sort);
getGroups.then((response) => { getGroups
this.updateGroups(response.json(), parentGroup); .then(response => response.json())
}) .then((response) => {
.finally(() => { this.isLoading = false;
this.isLoading = false;
}) this.updateGroups(response, parentGroup);
.catch(this.handleErrorResponse); })
.catch(this.handleErrorResponse);
return getGroups; return getGroups;
}, },
fetchPage(page, filterGroups, sort) { fetchPage(page, filterGroups, sort) {
this.isLoading = true; this.isLoading = true;
this.service.getGroups(null, page, filterGroups, sort) return this.service
.getGroups(null, page, filterGroups, sort)
.then((response) => { .then((response) => {
this.isLoading = false;
$.scrollTo(0);
this.updateGroups(response.json()); this.updateGroups(response.json());
this.updatePagination(response.headers); this.updatePagination(response.headers);
$.scrollTo(0);
})
.finally(() => {
this.isLoading = false;
}) })
.catch(this.handleErrorResponse); .catch(this.handleErrorResponse);
}, },
...@@ -104,19 +105,18 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -104,19 +105,18 @@ document.addEventListener('DOMContentLoaded', () => {
this.fetchGroups(parentGroup); this.fetchGroups(parentGroup);
} }
GroupsStore.toggleSubGroups(parentGroup); this.store.toggleSubGroups(parentGroup);
}, },
leaveGroup(group, collection) { leaveGroup(group, collection) {
this.service.leaveGroup(group.leavePath) this.service.leaveGroup(group.leavePath)
.then((response) => { .then((response) => {
$.scrollTo(0);
this.store.removeGroup(group, collection); this.store.removeGroup(group, collection);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash(response.json().notice, 'notice'); new Flash(response.json().notice, 'notice');
}) })
.finally(() => {
$.scrollTo(0);
})
.catch((response) => { .catch((response) => {
let message = 'An error occurred. Please try again.'; let message = 'An error occurred. Please try again.';
...@@ -135,10 +135,20 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -135,10 +135,20 @@ document.addEventListener('DOMContentLoaded', () => {
this.store.storePagination(headers); this.store.storePagination(headers);
}, },
handleErrorResponse() { handleErrorResponse() {
this.isLoading = false;
$.scrollTo(0);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash('An error occurred. Please try again.'); new Flash('An error occurred. Please try again.');
}, },
}, },
created() {
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleSubGroups', this.toggleSubGroups);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updateGroups', this.updateGroups);
eventHub.$on('updatePagination', this.updatePagination);
},
beforeMount() { beforeMount() {
let groupFilterList = null; let groupFilterList = null;
const form = document.querySelector('form#group-filter-form'); const form = document.querySelector('form#group-filter-form');
...@@ -155,19 +165,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -155,19 +165,11 @@ document.addEventListener('DOMContentLoaded', () => {
groupFilterList = new GroupFilterableList(opts); groupFilterList = new GroupFilterableList(opts);
groupFilterList.initSearch(); groupFilterList.initSearch();
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleSubGroups', this.toggleSubGroups);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updateGroups', this.updateGroups);
eventHub.$on('updatePagination', this.updatePagination);
}, },
mounted() { mounted() {
this.fetchGroups() this.fetchGroups()
.then((response) => { .then((response) => {
this.updatePagination(response.headers); this.updatePagination(response.headers);
})
.finally(() => {
this.isLoading = false; this.isLoading = false;
}) })
.catch(this.handleErrorResponse); .catch(this.handleErrorResponse);
......
...@@ -140,7 +140,7 @@ export default class GroupsStore { ...@@ -140,7 +140,7 @@ export default class GroupsStore {
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
static toggleSubGroups(toggleGroup) { toggleSubGroups(toggleGroup) {
const group = toggleGroup; const group = toggleGroup;
group.isOpen = !group.isOpen; group.isOpen = !group.isOpen;
return group; return group;
......
...@@ -340,21 +340,21 @@ ul.indent-list { ...@@ -340,21 +340,21 @@ ul.indent-list {
} }
} }
} }
.group-row { .group-row {
padding: 0; padding: 0;
border: none; border: none;
} }
.group-row-contents { .group-row-contents {
padding: 10px 10px 8px 10px; padding: 10px 10px 8px;
border-top: solid 1px transparent; border-top: solid 1px transparent;
border-bottom: solid 1px $white-normal; border-bottom: solid 1px $white-normal;
&:hover{ &:hover {
border-color: $row-hover-border; border-color: $row-hover-border;
background-color: $row-hover; background-color: $row-hover;
cursor: pointer; cursor: pointer;
} }
} }
} }
......
...@@ -3,39 +3,64 @@ import groupItemComponent from '~/groups/components/group_item.vue'; ...@@ -3,39 +3,64 @@ import groupItemComponent from '~/groups/components/group_item.vue';
import GroupsStore from '~/groups/stores/groups_store'; import GroupsStore from '~/groups/stores/groups_store';
import { group1 } from './mock_data'; import { group1 } from './mock_data';
fdescribe('Groups Component', () => { describe('Groups Component', () => {
let GroupItemComponent; let GroupItemComponent;
let component; let component;
let store; let store;
let group; let group;
beforeEach((done) => { describe('group with default data', () => {
GroupItemComponent = Vue.extend(groupItemComponent); beforeEach((done) => {
store = new GroupsStore(); GroupItemComponent = Vue.extend(groupItemComponent);
group = store.decorateGroup(group1); store = new GroupsStore();
group = store.decorateGroup(group1);
component = new GroupItemComponent({ component = new GroupItemComponent({
propsData: { propsData: {
group, group,
}, },
}).$mount(); }).$mount();
Vue.nextTick(() => { Vue.nextTick(() => {
done(); done();
});
}); });
});
it('should render the group item', () => { it('should render the group item correctly', () => {
expect(component.$el.classList.contains('group-row')).toBe(true); expect(component.$el.classList.contains('group-row')).toBe(true);
expect(component.$el.querySelector('.number-projects').textContent).toContain(group.numberProjects); expect(component.$el.classList.contains('.no-description')).toBe(false);
expect(component.$el.querySelector('.number-users').textContent).toContain(group.numberUsers); expect(component.$el.querySelector('.number-projects').textContent).toContain(group.numberProjects);
expect(component.$el.querySelector('.group-visibility')).toBeDefined(); expect(component.$el.querySelector('.number-users').textContent).toContain(group.numberUsers);
expect(component.$el.querySelector('.avatar-container')).toBeDefined(); expect(component.$el.querySelector('.group-visibility')).toBeDefined();
expect(component.$el.querySelector('.title').textContent).toContain(group.name); expect(component.$el.querySelector('.avatar-container')).toBeDefined();
expect(component.$el.querySelector('.description').textContent).toContain(group.description); expect(component.$el.querySelector('.title').textContent).toContain(group.name);
expect(component.$el.querySelector('.edit-group')).toBeDefined(); expect(component.$el.querySelector('.description').textContent).toContain(group.description);
expect(component.$el.querySelector('.leave-group')).toBeDefined(); expect(component.$el.querySelector('.edit-group')).toBeDefined();
expect(component.$el.querySelector('.leave-group')).toBeDefined();
});
}); });
// TODO: check for no description class when group has no description describe('group without description', () => {
beforeEach((done) => {
GroupItemComponent = Vue.extend(groupItemComponent);
store = new GroupsStore();
group1.description = '';
group = store.decorateGroup(group1);
component = new GroupItemComponent({
propsData: {
group,
},
}).$mount();
Vue.nextTick(() => {
done();
});
});
it('should render group item correctly', () => {
expect(component.$el.querySelector('.description').textContent).toBe('');
expect(component.$el.classList.contains('.no-description')).toBe(false);
});
});
}); });
...@@ -5,7 +5,7 @@ import groupsComponent from '~/groups/components/groups.vue'; ...@@ -5,7 +5,7 @@ import groupsComponent from '~/groups/components/groups.vue';
import GroupsStore from '~/groups/stores/groups_store'; import GroupsStore from '~/groups/stores/groups_store';
import { groupsData } from './mock_data'; import { groupsData } from './mock_data';
fdescribe('Groups Component', () => { describe('Groups Component', () => {
let GroupsComponent; let GroupsComponent;
let store; let store;
let component; let component;
......
...@@ -2,7 +2,7 @@ const group1 = { ...@@ -2,7 +2,7 @@ const group1 = {
id: '12', id: '12',
name: 'level1', name: 'level1',
path: 'level1', path: 'level1',
description: '', description: 'foo',
visibility: 'public', visibility: 'public',
avatar_url: null, avatar_url: null,
web_url: 'http://localhost:3000/groups/level1', web_url: 'http://localhost:3000/groups/level1',
...@@ -24,7 +24,7 @@ const group14 = { ...@@ -24,7 +24,7 @@ const group14 = {
id: 1128, id: 1128,
name: 'level4', name: 'level4',
path: 'level4', path: 'level4',
description: '', description: 'foo',
visibility: 'public', visibility: 'public',
avatar_url: null, avatar_url: null,
web_url: 'http://localhost:3000/groups/level1/level2/level3/level4', web_url: 'http://localhost:3000/groups/level1/level2/level3/level4',
...@@ -45,7 +45,7 @@ const group2 = { ...@@ -45,7 +45,7 @@ const group2 = {
id: 1119, id: 1119,
name: 'devops', name: 'devops',
path: 'devops', path: 'devops',
description: '', description: 'foo',
visibility: 'public', visibility: 'public',
avatar_url: null, avatar_url: null,
web_url: 'http://localhost:3000/groups/devops', web_url: 'http://localhost:3000/groups/devops',
...@@ -66,7 +66,7 @@ const group21 = { ...@@ -66,7 +66,7 @@ const group21 = {
id: 1120, id: 1120,
name: 'chef', name: 'chef',
path: 'chef', path: 'chef',
description: '', description: 'foo',
visibility: 'public', visibility: 'public',
avatar_url: null, avatar_url: null,
web_url: 'http://localhost:3000/groups/devops/chef', web_url: 'http://localhost:3000/groups/devops/chef',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment