Commit fe289694 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch...

Merge branch '281815-convert-project-members-list-view-from-haml-to-vue-setup-vue-application' into 'master'

Move some shared code to `members` directory [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!51520
parents 994c02bf db742fb7
export const GROUP_MEMBER_BASE_PROPERTY_NAME = 'group_member';
export const GROUP_MEMBER_ACCESS_LEVEL_PROPERTY_NAME = 'access_level';
export const GROUP_LINK_BASE_PROPERTY_NAME = 'group_link';
export const GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME = 'group_access';
import { isUndefined } from 'lodash';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import {
GROUP_MEMBER_BASE_PROPERTY_NAME,
GROUP_MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
GROUP_LINK_BASE_PROPERTY_NAME,
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME,
} from './constants';
export const parseDataAttributes = (el) => {
const { members, groupId, memberPath, canManageMembers } = el.dataset;
return {
members: convertObjectPropsToCamelCase(JSON.parse(members), { deep: true }),
sourceId: parseInt(groupId, 10),
memberPath,
canManageMembers: parseBoolean(canManageMembers),
};
};
const baseRequestFormatter = (basePropertyName, accessLevelPropertyName) => ({
accessLevel,
...otherProperties
}) => {
const accessLevelProperty = !isUndefined(accessLevel)
? { [accessLevelPropertyName]: accessLevel }
: {};
import { baseRequestFormatter } from '~/members/utils';
import { MEMBER_ACCESS_LEVEL_PROPERTY_NAME } from '~/members/constants';
import { GROUP_MEMBER_BASE_PROPERTY_NAME } from './constants';
return {
[basePropertyName]: {
...accessLevelProperty,
...otherProperties,
},
};
};
export const memberRequestFormatter = baseRequestFormatter(
export const groupMemberRequestFormatter = baseRequestFormatter(
GROUP_MEMBER_BASE_PROPERTY_NAME,
GROUP_MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
);
export const groupLinkRequestFormatter = baseRequestFormatter(
GROUP_LINK_BASE_PROPERTY_NAME,
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME,
MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
);
<script>
import { mapState, mapMutations } from 'vuex';
import { GlAlert } from '@gitlab/ui';
import MembersTable from '~/members/components/table/members_table.vue';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import MembersTable from './table/members_table.vue';
import FilterSortContainer from './filter_sort/filter_sort_container.vue';
import { HIDE_ERROR } from '../store/mutation_types';
import { scrollToElement } from '~/lib/utils/common_utils';
import { HIDE_ERROR } from '~/members/store/mutation_types';
export default {
name: 'GroupMembersApp',
name: 'MembersApp',
components: { MembersTable, FilterSortContainer, GlAlert },
computed: {
...mapState(['showError', 'errorMessage']),
......
......@@ -98,3 +98,8 @@ export const REMOVE_GROUP_LINK_MODAL_ID = 'remove-group-link-modal-id';
export const SEARCH_TOKEN_TYPE = 'filtered-search-term';
export const SORT_PARAM = 'sort';
export const MEMBER_ACCESS_LEVEL_PROPERTY_NAME = 'access_level';
export const GROUP_LINK_BASE_PROPERTY_NAME = 'group_link';
export const GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME = 'group_access';
import Vue from 'vue';
import Vuex from 'vuex';
import { GlToast } from '@gitlab/ui';
import { parseDataAttributes } from 'ee_else_ce/groups/members/utils';
import { parseDataAttributes } from 'ee_else_ce/members/utils';
import App from './components/app.vue';
import membersStore from '~/members/store';
import membersStore from './store';
export const initGroupMembersApp = (
export const initMembersApp = (
el,
{
tableFields = [],
......
import { isUndefined } from 'lodash';
import { __ } from '~/locale';
import { getParameterByName } from '~/lib/utils/common_utils';
import {
getParameterByName,
convertObjectPropsToCamelCase,
parseBoolean,
} from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { FIELDS, DEFAULT_SORT } from './constants';
import {
FIELDS,
DEFAULT_SORT,
GROUP_LINK_BASE_PROPERTY_NAME,
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME,
} from './constants';
export const generateBadges = (member, isCurrentUser) => [
{
......@@ -95,3 +105,35 @@ export const buildSortHref = ({
// Defined in `ee/app/assets/javascripts/vue_shared/components/members/utils.js`
export const canOverride = () => false;
export const parseDataAttributes = (el) => {
const { members, sourceId, memberPath, canManageMembers } = el.dataset;
return {
members: convertObjectPropsToCamelCase(JSON.parse(members), { deep: true }),
sourceId: parseInt(sourceId, 10),
memberPath,
canManageMembers: parseBoolean(canManageMembers),
};
};
export const baseRequestFormatter = (basePropertyName, accessLevelPropertyName) => ({
accessLevel,
...otherProperties
}) => {
const accessLevelProperty = !isUndefined(accessLevel)
? { [accessLevelPropertyName]: accessLevel }
: {};
return {
[basePropertyName]: {
...accessLevelProperty,
...otherProperties,
},
};
};
export const groupLinkRequestFormatter = baseRequestFormatter(
GROUP_LINK_BASE_PROPERTY_NAME,
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME,
);
......@@ -3,10 +3,11 @@ import memberExpirationDate from '~/member_expiration_date';
import UsersSelect from '~/users_select';
import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import { initGroupMembersApp } from '~/groups/members';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { memberRequestFormatter, groupLinkRequestFormatter } from '~/groups/members/utils';
import { initMembersApp } from '~/members/index';
import { groupMemberRequestFormatter } from '~/groups/members/utils';
import { groupLinkRequestFormatter } from '~/members/utils';
import { s__ } from '~/locale';
function mountRemoveMemberModal() {
......@@ -25,11 +26,11 @@ function mountRemoveMemberModal() {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initGroupMembersApp(document.querySelector('.js-group-members-list'), {
initMembersApp(document.querySelector('.js-group-members-list'), {
tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
requestFormatter: memberRequestFormatter,
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
......@@ -38,7 +39,8 @@ initGroupMembersApp(document.querySelector('.js-group-members-list'), {
recentSearchesStorageKey: 'group_members',
},
});
initGroupMembersApp(document.querySelector('.js-group-linked-list'), {
initMembersApp(document.querySelector('.js-group-group-links-list'), {
tableFields: SHARED_FIELDS.concat('granted'),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
......@@ -46,9 +48,9 @@ initGroupMembersApp(document.querySelector('.js-group-linked-list'), {
},
requestFormatter: groupLinkRequestFormatter,
});
initGroupMembersApp(document.querySelector('.js-group-invited-members-list'), {
initMembersApp(document.querySelector('.js-group-invited-members-list'), {
tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: memberRequestFormatter,
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
show: true,
tokens: [],
......@@ -57,9 +59,9 @@ initGroupMembersApp(document.querySelector('.js-group-invited-members-list'), {
recentSearchesStorageKey: 'group_invited_members',
},
});
initGroupMembersApp(document.querySelector('.js-group-access-requests-list'), {
initMembersApp(document.querySelector('.js-group-access-requests-list'), {
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: memberRequestFormatter,
requestFormatter: groupMemberRequestFormatter,
});
groupsSelect();
......
......@@ -13,7 +13,7 @@ module Groups::GroupMembersHelper
render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: group.access_level_roles, default_access_level: default_access_level
end
def linked_groups_data_json(group_links)
def group_group_links_data_json(group_links)
GroupGroupLinkSerializer.new.represent(group_links, { current_user: current_user }).to_json
end
......@@ -26,16 +26,16 @@ module Groups::GroupMembersHelper
{
members: members_data_json(group, members),
member_path: group_group_member_path(group, ':id'),
group_id: group.id,
source_id: group.id,
can_manage_members: can?(current_user, :admin_group_member, group).to_s
}
end
def linked_groups_list_data_attributes(group)
def group_group_links_list_data_attributes(group)
{
members: linked_groups_data_json(group.shared_with_group_links),
members: group_group_links_data_json(group.shared_with_group_links),
member_path: group_group_link_path(group, ':id'),
group_id: group.id
source_id: group.id
}
end
end
......
......@@ -66,7 +66,7 @@
= paginate @members, theme: 'gitlab', params: { invited_members_page: nil, search_invited: nil }
- if @group.shared_with_group_links.any?
#tab-groups.tab-pane
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.js-group-group-links-list{ data: group_group_links_list_data_attributes(@group) }
.loading
.spinner.spinner-md
- if show_invited_members
......
import { parseDataAttributes as CEParseDataAttributes } from '~/groups/members/utils';
export const parseDataAttributes = (el) => {
const { ldapOverridePath } = el.dataset;
return {
...CEParseDataAttributes(el),
ldapOverridePath,
};
};
import { __ } from '~/locale';
import { generateBadges as CEGenerateBadges } from '~/members/utils';
import {
generateBadges as CEGenerateBadges,
parseDataAttributes as CEParseDataAttributes,
} from '~/members/utils';
export {
isGroup,
......@@ -35,3 +38,12 @@ export const generateBadges = (member, isCurrentUser) => [
];
export const canOverride = (member) => member.canOverride;
export const parseDataAttributes = (el) => {
const { ldapOverridePath } = el.dataset;
return {
...CEParseDataAttributes(el),
ldapOverridePath,
};
};
import { parseDataAttributes } from 'ee/groups/members/utils';
import { membersJsonString, membersParsed } from 'jest/groups/members/mock_data';
describe('group member utils', () => {
describe('parseDataAttributes', () => {
let el;
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
el.setAttribute('data-can-manage-members', 'true');
el.setAttribute('data-ldap-override-path', '/groups/ldap-group/-/group_members/:id/override');
});
afterEach(() => {
el = null;
});
it('correctly parses the data attributes', () => {
expect(parseDataAttributes(el)).toEqual({
members: membersParsed,
sourceId: 234,
canManageMembers: true,
ldapOverridePath: '/groups/ldap-group/-/group_members/:id/override',
});
});
});
});
import { membersJsonString } from 'jest/groups/members/mock_data';
import { initGroupMembersApp } from '~/groups/members';
import { membersJsonString } from 'jest/members/mock_data';
import { initMembersApp } from '~/members/index';
describe('initGroupMembersApp', () => {
describe('initMembersApp', () => {
let el;
let vm;
const createVm = () => {
vm = initGroupMembersApp(el, {});
vm = initMembersApp(el, {});
};
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
el.setAttribute('data-source-id', '234');
el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id');
el.setAttribute('data-ldap-override-path', '/groups/ldap-group/-/group_members/:id/override');
});
......
import { member as memberMock } from 'jest/members/mock_data';
import { generateBadges, canOverride } from 'ee/members/utils';
import { member as memberMock, membersJsonString, members } from 'jest/members/mock_data';
import { generateBadges, canOverride, parseDataAttributes } from 'ee/members/utils';
describe('Members Utils', () => {
describe('generateBadges', () => {
......@@ -37,4 +37,34 @@ describe('Members Utils', () => {
expect(canOverride(member)).toBe(expected);
});
});
describe('group member utils', () => {
describe('parseDataAttributes', () => {
let el;
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-source-id', '234');
el.setAttribute('data-can-manage-members', 'true');
el.setAttribute(
'data-ldap-override-path',
'/groups/ldap-group/-/group_members/:id/override',
);
});
afterEach(() => {
el = null;
});
it('correctly parses the data attributes', () => {
expect(parseDataAttributes(el)).toEqual({
members,
sourceId: 234,
canManageMembers: true,
ldapOverridePath: '/groups/ldap-group/-/group_members/:id/override',
});
});
});
});
});
export const membersJsonString =
'[{"requested_at":null,"can_update":true,"can_remove":true,"can_override":false,"access_level":{"integer_value":50,"string_value":"Owner"},"source":{"id":323,"name":"My group / my subgroup","web_url":"http://127.0.0.1:3000/groups/my-group/my-subgroup"},"user":{"id":1,"name":"Administrator","username":"root","web_url":"http://127.0.0.1:3000/root","avatar_url":"https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80\u0026d=identicon","blocked":false,"two_factor_enabled":false},"id":524,"created_at":"2020-08-21T21:33:27.631Z","expires_at":null,"using_license":false,"group_sso":false,"group_managed_account":false}]';
export const membersParsed = [
{
requestedAt: null,
canUpdate: true,
canRemove: true,
canOverride: false,
accessLevel: { integerValue: 50, stringValue: 'Owner' },
source: {
id: 323,
name: 'My group / my subgroup',
webUrl: 'http://127.0.0.1:3000/groups/my-group/my-subgroup',
},
user: {
id: 1,
name: 'Administrator',
username: 'root',
webUrl: 'http://127.0.0.1:3000/root',
avatarUrl:
'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon',
blocked: false,
twoFactorEnabled: false,
},
id: 524,
createdAt: '2020-08-21T21:33:27.631Z',
expiresAt: null,
usingLicense: false,
groupSso: false,
groupManagedAccount: false,
},
];
import { membersJsonString, membersParsed } from './mock_data';
import {
parseDataAttributes,
memberRequestFormatter,
groupLinkRequestFormatter,
} from '~/groups/members/utils';
import { groupMemberRequestFormatter } from '~/groups/members/utils';
describe('group member utils', () => {
describe('parseDataAttributes', () => {
let el;
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
el.setAttribute('data-can-manage-members', 'true');
});
afterEach(() => {
el = null;
});
it('correctly parses the data attributes', () => {
expect(parseDataAttributes(el)).toEqual({
members: membersParsed,
sourceId: 234,
canManageMembers: true,
});
});
});
describe('memberRequestFormatter', () => {
describe('groupMemberRequestFormatter', () => {
it('returns expected format', () => {
expect(
memberRequestFormatter({
groupMemberRequestFormatter({
accessLevel: 50,
expires_at: '2020-10-16',
}),
).toEqual({ group_member: { access_level: 50, expires_at: '2020-10-16' } });
});
});
describe('groupLinkRequestFormatter', () => {
it('returns expected format', () => {
expect(
groupLinkRequestFormatter({
accessLevel: 50,
expires_at: '2020-10-16',
}),
).toEqual({ group_link: { group_access: 50, expires_at: '2020-10-16' } });
});
});
});
......@@ -2,13 +2,13 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import { GlAlert } from '@gitlab/ui';
import App from '~/groups/members/components/app.vue';
import MembersApp from '~/members/components/app.vue';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import * as commonUtils from '~/lib/utils/common_utils';
import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types';
import mutations from '~/members/store/mutations';
describe('GroupMembersApp', () => {
describe('MembersApp', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -25,7 +25,7 @@ describe('GroupMembersApp', () => {
mutations,
});
wrapper = shallowMount(App, {
wrapper = shallowMount(MembersApp, {
localVue,
store,
...options,
......
import { createWrapper } from '@vue/test-utils';
import { initGroupMembersApp } from '~/groups/members';
import GroupMembersApp from '~/groups/members/components/app.vue';
import { membersJsonString, membersParsed } from './mock_data';
import { initMembersApp } from '~/members/index';
import MembersApp from '~/members/components/app.vue';
import { membersJsonString, members } from './mock_data';
describe('initGroupMembersApp', () => {
describe('initMembersApp', () => {
let el;
let vm;
let wrapper;
const setup = () => {
vm = initGroupMembersApp(el, {
vm = initMembersApp(el, {
tableFields: ['account'],
tableAttrs: { table: { 'data-qa-selector': 'members_list' } },
tableSortableFields: ['account'],
......@@ -22,7 +22,7 @@ describe('initGroupMembersApp', () => {
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-group-id', '234');
el.setAttribute('data-source-id', '234');
el.setAttribute('data-can-manage-members', 'true');
el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id');
......@@ -36,10 +36,10 @@ describe('initGroupMembersApp', () => {
wrapper = null;
});
it('renders `GroupMembersApp`', () => {
it('renders `MembersApp`', () => {
setup();
expect(wrapper.find(GroupMembersApp).exists()).toBe(true);
expect(wrapper.find(MembersApp).exists()).toBe(true);
});
it('sets `currentUserId` in Vuex store', () => {
......@@ -57,7 +57,7 @@ describe('initGroupMembersApp', () => {
});
});
it('parses and sets `data-group-id` as `sourceId` in Vuex store', () => {
it('parses and sets `data-source-id` as `sourceId` in Vuex store', () => {
setup();
expect(vm.$store.state.sourceId).toBe(234);
......@@ -72,7 +72,7 @@ describe('initGroupMembersApp', () => {
it('parses and sets `members` in Vuex store', () => {
setup();
expect(vm.$store.state.members).toEqual(membersParsed);
expect(vm.$store.state.members).toEqual(members);
});
it('sets `tableFields` in Vuex store', () => {
......
......@@ -69,3 +69,5 @@ export const accessRequest = {
};
export const members = [member];
export const membersJsonString = JSON.stringify(members);
......@@ -9,9 +9,11 @@ import {
canOverride,
parseSortParam,
buildSortHref,
parseDataAttributes,
groupLinkRequestFormatter,
} from '~/members/utils';
import { DEFAULT_SORT } from '~/members/constants';
import { member as memberMock, group, invite } from './mock_data';
import { member as memberMock, group, invite, membersJsonString, members } from './mock_data';
const DIRECT_MEMBER_ID = 178;
const INHERITED_MEMBER_ID = 179;
......@@ -229,4 +231,38 @@ describe('Members Utils', () => {
});
});
});
describe('parseDataAttributes', () => {
let el;
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
el.setAttribute('data-source-id', '234');
el.setAttribute('data-can-manage-members', 'true');
});
afterEach(() => {
el = null;
});
it('correctly parses the data attributes', () => {
expect(parseDataAttributes(el)).toEqual({
members,
sourceId: 234,
canManageMembers: true,
});
});
});
describe('groupLinkRequestFormatter', () => {
it('returns expected format', () => {
expect(
groupLinkRequestFormatter({
accessLevel: 50,
expires_at: '2020-10-16',
}),
).toEqual({ group_link: { group_access: 50, expires_at: '2020-10-16' } });
});
});
});
......@@ -23,11 +23,11 @@ RSpec.describe Groups::GroupMembersHelper do
end
end
describe '#linked_groups_data_json' do
describe '#group_group_links_data_json' do
include_context 'group_group_link'
it 'matches json schema' do
json = helper.linked_groups_data_json(shared_group.shared_with_group_links)
json = helper.group_group_links_data_json(shared_group.shared_with_group_links)
expect(json).to match_schema('group_group_links')
end
......@@ -81,13 +81,13 @@ RSpec.describe Groups::GroupMembersHelper do
expect(helper.group_members_list_data_attributes(group, present_members([group_member]))).to include({
members: helper.members_data_json(group, present_members([group_member])),
member_path: '/groups/foo-bar/-/group_members/:id',
group_id: group.id,
source_id: group.id,
can_manage_members: 'true'
})
end
end
describe '#linked_groups_list_data_attributes' do
describe '#group_group_links_list_data_attributes' do
include_context 'group_group_link'
before do
......@@ -95,10 +95,10 @@ RSpec.describe Groups::GroupMembersHelper do
end
it 'returns expected hash' do
expect(helper.linked_groups_list_data_attributes(shared_group)).to include({
members: helper.linked_groups_data_json(shared_group.shared_with_group_links),
expect(helper.group_group_links_list_data_attributes(shared_group)).to include({
members: helper.group_group_links_data_json(shared_group.shared_with_group_links),
member_path: '/groups/foo-bar/-/group_links/:id',
group_id: shared_group.id
source_id: shared_group.id
})
end
end
......
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