summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/groups
diff options
context:
space:
mode:
authorDennis Tang <dennis@dennistang.net>2018-09-07 06:09:13 +0000
committerMike Greiling <mike@pixelcog.com>2018-09-07 06:09:13 +0000
commit5b74a1aebcc1712316b8269c415e83e9d59750d5 (patch)
treea398af6332ae6fd8165981c63aecd7602a338560 /app/assets/javascripts/groups
parent53fae9ad84361bbf3f9fb3db446c9bb22772fb64 (diff)
downloadgitlab-ce-5b74a1aebcc1712316b8269c415e83e9d59750d5.tar.gz
Resolve "Improve handling of projects shared with a group"
Diffstat (limited to 'app/assets/javascripts/groups')
-rw-r--r--app/assets/javascripts/groups/components/app.vue78
-rw-r--r--app/assets/javascripts/groups/components/group_folder.vue7
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue14
-rw-r--r--app/assets/javascripts/groups/components/groups.vue68
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue7
-rw-r--r--app/assets/javascripts/groups/constants.js24
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js64
-rw-r--r--app/assets/javascripts/groups/index.js31
8 files changed, 205 insertions, 88 deletions
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index b0765747a36..69f192ac75e 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -2,14 +2,15 @@
/* global Flash */
import $ from 'jquery';
-import { s__ } from '~/locale';
+import { s__, sprintf } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { HIDDEN_CLASS } from '~/lib/utils/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
-import { COMMON_STR } from '../constants';
+import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';
import groupsComponent from './groups.vue';
export default {
@@ -19,6 +20,16 @@ export default {
groupsComponent,
},
props: {
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ containerId: {
+ type: String,
+ required: false,
+ default: '',
+ },
store: {
type: Object,
required: true,
@@ -56,31 +67,28 @@ export default {
? COMMON_STR.GROUP_SEARCH_EMPTY
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
- eventHub.$on('fetchPage', this.fetchPage);
- eventHub.$on('toggleChildren', this.toggleChildren);
- eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$on('updatePagination', this.updatePagination);
- eventHub.$on('updateGroups', this.updateGroups);
+ eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$on(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$on(`${this.action}updateGroups`, this.updateGroups);
},
mounted() {
this.fetchAllGroups();
+
+ if (this.containerId) {
+ this.containerEl = document.getElementById(this.containerId);
+ }
},
beforeDestroy() {
- eventHub.$off('fetchPage', this.fetchPage);
- eventHub.$off('toggleChildren', this.toggleChildren);
- eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$off('updatePagination', this.updatePagination);
- eventHub.$off('updateGroups', this.updateGroups);
+ eventHub.$off(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$off(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$off(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$off(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$off(`${this.action}updateGroups`, this.updateGroups);
},
methods: {
- fetchGroups({
- parentId,
- page,
- filterGroupsBy,
- sortBy,
- archived,
- updatePagination,
- }) {
+ fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
.then(res => {
@@ -165,13 +173,13 @@ export default {
}
},
showLeaveGroupModal(group, parentGroup) {
+ const { fullName } = group;
this.targetGroup = group;
this.targetParentGroup = parentGroup;
this.showModal = true;
- this.groupLeaveConfirmationMessage = s__(
- `GroupsTree|Are you sure you want to leave the "${
- group.fullName
- }" group?`,
+ this.groupLeaveConfirmationMessage = sprintf(
+ s__('GroupsTree|Are you sure you want to leave the "%{fullName}" group?'),
+ { fullName },
);
},
hideLeaveGroupModal() {
@@ -197,16 +205,35 @@ export default {
this.targetGroup.isBeingRemoved = false;
});
},
+ showEmptyState() {
+ const { containerEl } = this;
+ const contentListEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ const emptyStateEl = containerEl.querySelector('.empty-state');
+
+ if (contentListEl) {
+ contentListEl.remove();
+ }
+
+ if (emptyStateEl) {
+ emptyStateEl.classList.remove(HIDDEN_CLASS);
+ }
+ },
updatePagination(headers) {
this.store.setPaginationInfo(headers);
},
updateGroups(groups, fromSearch) {
- this.isSearchEmpty = groups ? groups.length === 0 : false;
+ const hasGroups = groups && groups.length > 0;
+ this.isSearchEmpty = !hasGroups;
+
if (fromSearch) {
this.store.setSearchedGroups(groups);
} else {
this.store.setGroups(groups);
}
+
+ if (this.action && !hasGroups && !fromSearch) {
+ this.showEmptyState();
+ }
},
},
};
@@ -226,6 +253,7 @@ export default {
:search-empty="isSearchEmpty"
:search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
+ :action="action"
/>
<deprecated-modal
v-show="showModal"
diff --git a/app/assets/javascripts/groups/components/group_folder.vue b/app/assets/javascripts/groups/components/group_folder.vue
index 647c9d0046d..bcc7a638346 100644
--- a/app/assets/javascripts/groups/components/group_folder.vue
+++ b/app/assets/javascripts/groups/components/group_folder.vue
@@ -11,8 +11,12 @@ export default {
},
groups: {
type: Array,
+ required: true,
+ },
+ action: {
+ type: String,
required: false,
- default: () => ([]),
+ default: '',
},
},
computed: {
@@ -37,6 +41,7 @@ export default {
:key="index"
:group="group"
:parent-group="parentGroup"
+ :action="action"
/>
<li
v-if="hasMoreChildren"
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 2b9e2a929fc..154ad2ea607 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -30,6 +30,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
groupDomId() {
@@ -56,10 +61,12 @@ export default {
methods: {
onClickRowGroup(e) {
const NO_EXPAND_CLS = 'no-expand';
- if (!(e.target.classList.contains(NO_EXPAND_CLS) ||
- e.target.parentElement.classList.contains(NO_EXPAND_CLS))) {
+ const targetClasses = e.target.classList;
+ const parentElClasses = e.target.parentElement.classList;
+
+ if (!(targetClasses.contains(NO_EXPAND_CLS) || parentElClasses.contains(NO_EXPAND_CLS))) {
if (this.hasChildren) {
- eventHub.$emit('toggleChildren', this.group);
+ eventHub.$emit(`${this.action}toggleChildren`, this.group);
} else {
visitUrl(this.group.relativePath);
}
@@ -158,6 +165,7 @@ export default {
v-if="group.isOpen && hasChildren"
:parent-group="group"
:groups="group.children"
+ :action="action"
/>
</li>
</template>
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index 73ae928b0d9..a1beb222950 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -1,39 +1,44 @@
<script>
- import tablePagination from '~/vue_shared/components/table_pagination.vue';
- import eventHub from '../event_hub';
- import { getParameterByName } from '../../lib/utils/common_utils';
+import tablePagination from '~/vue_shared/components/table_pagination.vue';
+import eventHub from '../event_hub';
+import { getParameterByName } from '../../lib/utils/common_utils';
- export default {
- components: {
- tablePagination,
+export default {
+ components: {
+ tablePagination,
+ },
+ props: {
+ groups: {
+ type: Array,
+ required: true,
},
- props: {
- groups: {
- type: Array,
- required: true,
- },
- pageInfo: {
- type: Object,
- required: true,
- },
- searchEmpty: {
- type: Boolean,
- required: true,
- },
- searchEmptyMessage: {
- type: String,
- required: true,
- },
+ pageInfo: {
+ type: Object,
+ required: true,
},
- methods: {
- change(page) {
- const filterGroupsParam = getParameterByName('filter_groups');
- const sortParam = getParameterByName('sort');
- const archivedParam = getParameterByName('archived');
- eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
- },
+ searchEmpty: {
+ type: Boolean,
+ required: true,
},
- };
+ searchEmptyMessage: {
+ type: String,
+ required: true,
+ },
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ change(page) {
+ const filterGroupsParam = getParameterByName('filter_groups');
+ const sortParam = getParameterByName('sort');
+ const archivedParam = getParameterByName('archived');
+ eventHub.$emit(`${this.action}fetchPage`, page, filterGroupsParam, sortParam, archivedParam);
+ },
+ },
+};
</script>
<template>
@@ -47,6 +52,7 @@
<group-folder
v-if="!searchEmpty"
:groups="groups"
+ :action="action"
/>
<table-pagination
v-if="!searchEmpty"
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 24eec4901ec..6e700b8bf8a 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -21,6 +21,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
leaveBtnTitle() {
@@ -32,7 +37,7 @@ export default {
},
methods: {
onLeaveGroup() {
- eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
+ eventHub.$emit(`${this.action}showLeaveGroupModal`, this.group, this.parentGroup);
},
},
};
diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js
index b8baed682f5..9c246cf3ba6 100644
--- a/app/assets/javascripts/groups/constants.js
+++ b/app/assets/javascripts/groups/constants.js
@@ -2,13 +2,23 @@ import { __, s__ } from '../locale';
export const MAX_CHILDREN_COUNT = 20;
+export const ACTIVE_TAB_SUBGROUPS_AND_PROJECTS = 'subgroups_and_projects';
+export const ACTIVE_TAB_SHARED = 'shared';
+export const ACTIVE_TAB_ARCHIVED = 'archived';
+
+export const GROUPS_LIST_HOLDER_CLASS = '.js-groups-list-holder';
+export const GROUPS_FILTER_FORM_CLASS = '.js-group-filter-form';
+export const CONTENT_LIST_CLASS = '.content-list';
+
export const COMMON_STR = {
FAILURE: __('An error occurred. Please try again.'),
- LEAVE_FORBIDDEN: s__('GroupsTree|Failed to leave the group. Please make sure you are not the only owner.'),
+ LEAVE_FORBIDDEN: s__(
+ 'GroupsTree|Failed to leave the group. Please make sure you are not the only owner.',
+ ),
LEAVE_BTN_TITLE: s__('GroupsTree|Leave this group'),
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
- GROUP_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups matched your search'),
- GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups or projects matched your search'),
+ GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
+ GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
};
export const ITEM_TYPE = {
@@ -17,8 +27,12 @@ export const ITEM_TYPE = {
};
export const GROUP_VISIBILITY_TYPE = {
- public: __('Public - The group and any public projects can be viewed without any authentication.'),
- internal: __('Internal - The group and any internal projects can be viewed by any logged in user.'),
+ public: __(
+ 'Public - The group and any public projects can be viewed without any authentication.',
+ ),
+ internal: __(
+ 'Internal - The group and any internal projects can be viewed by any logged in user.',
+ ),
private: __('Private - The group and its projects can only be viewed by members.'),
};
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index e6db1746487..693519729ac 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -4,13 +4,23 @@ import eventHub from './event_hub';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
- constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
+ constructor({
+ form,
+ filter,
+ holder,
+ filterEndpoint,
+ pagePath,
+ dropdownSel,
+ filterInputField,
+ action,
+ }) {
super(form, filter, holder, filterInputField);
this.form = form;
this.filterEndpoint = filterEndpoint;
this.pagePath = pagePath;
this.filterInputField = filterInputField;
this.$dropdown = $(dropdownSel);
+ this.action = action;
}
getFilterEndpoint() {
@@ -20,15 +30,16 @@ export default class GroupFilterableList extends FilterableList {
getPagePath(queryData) {
const params = queryData ? $.param(queryData) : '';
const queryString = params ? `?${params}` : '';
- return `${this.pagePath}${queryString}`;
+ const path = this.pagePath || window.location.pathname;
+ return `${path}${queryString}`;
}
bindEvents() {
super.bindEvents();
- this.onFilterOptionClikWrapper = this.onOptionClick.bind(this);
+ this.onFilterOptionClickWrapper = this.onOptionClick.bind(this);
- this.$dropdown.on('click', 'a', this.onFilterOptionClikWrapper);
+ this.$dropdown.on('click', 'a', this.onFilterOptionClickWrapper);
}
onFilterInput() {
@@ -53,7 +64,12 @@ export default class GroupFilterableList extends FilterableList {
}
setDefaultFilterOption() {
- const defaultOption = $.trim(this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').first().text());
+ const defaultOption = $.trim(
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-sort-order a')
+ .first()
+ .text(),
+ );
this.$dropdown.find('.dropdown-label').text(defaultOption);
}
@@ -65,11 +81,19 @@ export default class GroupFilterableList extends FilterableList {
// Get type of option selected from dropdown
const currentTargetClassList = e.currentTarget.parentElement.classList;
const isOptionFilterBySort = currentTargetClassList.contains('js-filter-sort-order');
- const isOptionFilterByArchivedProjects = currentTargetClassList.contains('js-filter-archived-projects');
+ const isOptionFilterByArchivedProjects = currentTargetClassList.contains(
+ 'js-filter-archived-projects',
+ );
// Get option query param, also preserve currently applied query param
- const sortParam = getParameterByName('sort', isOptionFilterBySort ? e.currentTarget.href : window.location.href);
- const archivedParam = getParameterByName('archived', isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href);
+ const sortParam = getParameterByName(
+ 'sort',
+ isOptionFilterBySort ? e.currentTarget.href : window.location.href,
+ );
+ const archivedParam = getParameterByName(
+ 'archived',
+ isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
+ );
if (sortParam) {
queryData.sort = sortParam;
@@ -86,7 +110,9 @@ export default class GroupFilterableList extends FilterableList {
this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text));
this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').removeClass('is-active');
} else if (isOptionFilterByArchivedProjects) {
- this.$dropdown.find('.dropdown-menu li.js-filter-archived-projects a').removeClass('is-active');
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-archived-projects a')
+ .removeClass('is-active');
}
$(e.target).addClass('is-active');
@@ -98,11 +124,19 @@ export default class GroupFilterableList extends FilterableList {
onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
- window.history.replaceState({
- page: currentPath,
- }, document.title, currentPath);
-
- eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
- eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
+ window.history.replaceState(
+ {
+ page: currentPath,
+ },
+ document.title,
+ currentPath,
+ );
+
+ eventHub.$emit(
+ `${this.action}updateGroups`,
+ res.data,
+ Object.prototype.hasOwnProperty.call(queryData, this.filterInputField),
+ );
+ eventHub.$emit(`${this.action}updatePagination`, normalizeHeaders(res.headers));
}
}
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index 83a9008a94b..0f68f05b523 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -7,18 +7,26 @@ import GroupsService from './service/groups_service';
import groupsApp from './components/app.vue';
import groupFolderComponent from './components/group_folder.vue';
import groupItemComponent from './components/group_item.vue';
+import { GROUPS_LIST_HOLDER_CLASS, CONTENT_LIST_CLASS } from './constants';
Vue.use(Translate);
-export default () => {
- const el = document.getElementById('js-groups-tree');
+export default (containerId = 'js-groups-tree', endpoint, action = '') => {
+ const containerEl = document.getElementById(containerId);
+ let dataEl;
// Don't do anything if element doesn't exist (No groups)
// This is for when the user enters directly to the page via URL
- if (!el) {
+ if (!containerEl) {
return;
}
+ const el = action ? containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS) : containerEl;
+
+ if (action) {
+ dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ }
+
Vue.component('group-folder', groupFolderComponent);
Vue.component('group-item', groupItemComponent);
@@ -29,20 +37,26 @@ export default () => {
groupsApp,
},
data() {
- const { dataset } = this.$options.el;
+ const { dataset } = dataEl || this.$options.el;
const hideProjects = dataset.hideProjects === 'true';
+ const service = new GroupsService(endpoint || dataset.endpoint);
const store = new GroupsStore(hideProjects);
- const service = new GroupsService(dataset.endpoint);
return {
+ action,
store,
service,
hideProjects,
loading: true,
+ containerId,
};
},
beforeMount() {
- const { dataset } = this.$options.el;
+ if (this.action) {
+ return;
+ }
+
+ const { dataset } = dataEl || this.$options.el;
let groupFilterList = null;
const form = document.querySelector(dataset.formSel);
const filter = document.querySelector(dataset.filterSel);
@@ -52,10 +66,11 @@ export default () => {
form,
filter,
holder,
- filterEndpoint: dataset.endpoint,
+ filterEndpoint: endpoint || dataset.endpoint,
pagePath: dataset.path,
dropdownSel: dataset.dropdownSel,
filterInputField: 'filter',
+ action: this.action,
};
groupFilterList = new GroupFilterableList(opts);
@@ -64,9 +79,11 @@ export default () => {
render(createElement) {
return createElement('groups-app', {
props: {
+ action: this.action,
store: this.store,
service: this.service,
hideProjects: this.hideProjects,
+ containerId: this.containerId,
},
});
},