Commit 9fa4acc8 authored by Simon Knox's avatar Simon Knox

Merge branch...

Merge branch '332643-combine-access-expires-and-expiration-into-one-column-on-group-members-table' into 'master'

Remove "Access expires" column from group/project members list

See merge request gitlab-org/gitlab!70702
parents 683f739f 9330f658
<script>
import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import {
approximateDuration,
differenceInSeconds,
formatDate,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import { DAYS_TO_EXPIRE_SOON } from '../../constants';
export default {
name: 'ExpiresAt',
components: { GlSprintf },
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
date: {
type: String,
required: false,
default: null,
},
},
computed: {
noExpirationSet() {
return this.date === null;
},
parsed() {
return new Date(this.date);
},
differenceInSeconds() {
return differenceInSeconds(new Date(), this.parsed);
},
isExpired() {
return this.differenceInSeconds <= 0;
},
inWords() {
return approximateDuration(this.differenceInSeconds);
},
formatted() {
return formatDate(this.parsed);
},
expiresSoon() {
return getDayDifference(new Date(), this.parsed) < DAYS_TO_EXPIRE_SOON;
},
cssClass() {
return {
'gl-text-red-500': this.isExpired,
'gl-text-orange-500': this.expiresSoon,
};
},
},
};
</script>
<template>
<span v-if="noExpirationSet">{{ s__('Members|No expiration set') }}</span>
<span v-else v-gl-tooltip.hover :title="formatted" :class="cssClass">
<template v-if="isExpired">{{ s__('Members|Expired') }}</template>
<gl-sprintf v-else :message="s__('Members|in %{time}')">
<template #time>
{{ inWords }}
</template>
</gl-sprintf>
</span>
</template>
...@@ -10,7 +10,6 @@ import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue'; ...@@ -10,7 +10,6 @@ import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue';
import RemoveMemberModal from '../modals/remove_member_modal.vue'; import RemoveMemberModal from '../modals/remove_member_modal.vue';
import CreatedAt from './created_at.vue'; import CreatedAt from './created_at.vue';
import ExpirationDatepicker from './expiration_datepicker.vue'; import ExpirationDatepicker from './expiration_datepicker.vue';
import ExpiresAt from './expires_at.vue';
import MemberActionButtons from './member_action_buttons.vue'; import MemberActionButtons from './member_action_buttons.vue';
import MemberAvatar from './member_avatar.vue'; import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue'; import MemberSource from './member_source.vue';
...@@ -24,7 +23,6 @@ export default { ...@@ -24,7 +23,6 @@ export default {
GlPagination, GlPagination,
MemberAvatar, MemberAvatar,
CreatedAt, CreatedAt,
ExpiresAt,
MembersTableCell, MembersTableCell,
MemberSource, MemberSource,
MemberActionButtons, MemberActionButtons,
...@@ -182,10 +180,6 @@ export default { ...@@ -182,10 +180,6 @@ export default {
<created-at :date="createdAt" /> <created-at :date="createdAt" />
</template> </template>
<template #cell(expires)="{ item: { expiresAt } }">
<expires-at :date="expiresAt" />
</template>
<template #cell(maxRole)="{ item: member }"> <template #cell(maxRole)="{ item: member }">
<members-table-cell #default="{ permissions }" :member="member"> <members-table-cell #default="{ permissions }" :member="member">
<role-dropdown v-if="permissions.canUpdate" :permissions="permissions" :member="member" /> <role-dropdown v-if="permissions.canUpdate" :permissions="permissions" :member="member" />
......
...@@ -37,12 +37,6 @@ export const FIELDS = [ ...@@ -37,12 +37,6 @@ export const FIELDS = [
thClass: 'col-meta', thClass: 'col-meta',
tdClass: 'col-meta', tdClass: 'col-meta',
}, },
{
key: 'expires',
label: __('Access expires'),
thClass: 'col-meta',
tdClass: 'col-meta',
},
{ {
key: 'maxRole', key: 'maxRole',
label: __('Max role'), label: __('Max role'),
......
...@@ -11,7 +11,7 @@ import { MEMBER_TYPES } from '~/members/constants'; ...@@ -11,7 +11,7 @@ import { MEMBER_TYPES } from '~/members/constants';
import { groupLinkRequestFormatter } from '~/members/utils'; import { groupLinkRequestFormatter } from '~/members/utils';
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-group-members-list-app'), { initMembersApp(document.querySelector('.js-group-members-list-app'), {
[MEMBER_TYPES.user]: { [MEMBER_TYPES.user]: {
......
...@@ -26,7 +26,7 @@ initInviteMembersForm(); ...@@ -26,7 +26,7 @@ initInviteMembersForm();
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-project-members-list-app'), { initMembersApp(document.querySelector('.js-project-members-list-app'), {
[MEMBER_TYPES.user]: { [MEMBER_TYPES.user]: {
tableFields: SHARED_FIELDS.concat(['source', 'granted']), tableFields: SHARED_FIELDS.concat(['source', 'granted']),
......
...@@ -21212,9 +21212,6 @@ msgstr "" ...@@ -21212,9 +21212,6 @@ msgstr ""
msgid "Members|Expiration date updated successfully." msgid "Members|Expiration date updated successfully."
msgstr "" msgstr ""
msgid "Members|Expired"
msgstr ""
msgid "Members|Filter members" msgid "Members|Filter members"
msgstr "" msgstr ""
...@@ -21230,9 +21227,6 @@ msgstr "" ...@@ -21230,9 +21227,6 @@ msgstr ""
msgid "Members|Membership" msgid "Members|Membership"
msgstr "" msgstr ""
msgid "Members|No expiration set"
msgstr ""
msgid "Members|Remove \"%{groupName}\"" msgid "Members|Remove \"%{groupName}\""
msgstr "" msgstr ""
...@@ -21254,9 +21248,6 @@ msgstr "" ...@@ -21254,9 +21248,6 @@ msgstr ""
msgid "Members|Search invited" msgid "Members|Search invited"
msgstr "" msgstr ""
msgid "Members|in %{time}"
msgstr ""
msgid "Member|Deny access" msgid "Member|Deny access"
msgstr "" msgstr ""
......
...@@ -63,6 +63,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js do ...@@ -63,6 +63,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
context 'when group link exists' do context 'when group link exists' do
let_it_be(:shared_with_group) { create(:group) } let_it_be(:shared_with_group) { create(:group) }
let_it_be(:shared_group) { create(:group) } let_it_be(:shared_group) { create(:group) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
let(:additional_link_attrs) { {} } let(:additional_link_attrs) { {} }
...@@ -115,29 +116,29 @@ RSpec.describe 'Groups > Members > Manage groups', :js do ...@@ -115,29 +116,29 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
click_groups_tab click_groups_tab
page.within first_row do page.within first_row do
fill_in 'Expiration date', with: 5.days.from_now.to_date fill_in 'Expiration date', with: expiration_date
find_field('Expiration date').native.send_keys :enter find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
end end
end end
context 'when expiry date is set' do context 'when expiry date is set' do
let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } } let(:additional_link_attrs) { { expires_at: expiration_date } }
it 'clears expiry date' do it 'clears expiry date' do
click_groups_tab click_groups_tab
page.within first_row do page.within first_row do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
find('[data-testid="clear-button"]').click find('[data-testid="clear-button"]').click
wait_for_requests wait_for_requests
expect(page).to have_content('No expiration set') expect(page).to have_field('Expiration date', with: '')
end end
end end
end end
......
...@@ -8,6 +8,7 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js ...@@ -8,6 +8,7 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
let_it_be(:user1) { create(:user, name: 'John Doe') } let_it_be(:user1) { create(:user, name: 'John Doe') }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
let(:new_member) { create(:user, name: 'Mary Jane') } let(:new_member) { create(:user, name: 'Mary Jane') }
...@@ -19,10 +20,10 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js ...@@ -19,10 +20,10 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
it 'expiration date is displayed in the members list' do it 'expiration date is displayed in the members list' do
visit group_group_members_path(group) visit group_group_members_path(group)
invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date) invite_member(new_member.name, role: 'Guest', expires_at: expiration_date)
page.within second_row do page.within second_row do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
end end
end end
...@@ -31,27 +32,27 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js ...@@ -31,27 +32,27 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
visit group_group_members_path(group) visit group_group_members_path(group)
page.within second_row do page.within second_row do
fill_in 'Expiration date', with: 5.days.from_now.to_date fill_in 'Expiration date', with: expiration_date
find_field('Expiration date').native.send_keys :enter find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
end end
end end
it 'clears expiration date' do it 'clears expiration date' do
create(:group_member, :developer, user: new_member, group: group, expires_at: 5.days.from_now.to_date) create(:group_member, :developer, user: new_member, group: group, expires_at: expiration_date)
visit group_group_members_path(group) visit group_group_members_path(group)
page.within second_row do page.within second_row do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
find('[data-testid="clear-button"]').click find('[data-testid="clear-button"]').click
wait_for_requests wait_for_requests
expect(page).to have_content('No expiration set') expect(page).to have_field('Expiration date', with: '')
end end
end end
end end
...@@ -8,6 +8,7 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do ...@@ -8,6 +8,7 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
let(:additional_link_attrs) { {} } let(:additional_link_attrs) { {} }
let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) } let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) }
...@@ -37,27 +38,27 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do ...@@ -37,27 +38,27 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
it 'updates expiry date' do it 'updates expiry date' do
page.within find_group_row(group) do page.within find_group_row(group) do
fill_in 'Expiration date', with: 5.days.from_now.to_date fill_in 'Expiration date', with: expiration_date
find_field('Expiration date').native.send_keys :enter find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
end end
end end
context 'when link has expiry date set' do context 'when link has expiry date set' do
let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } } let(:additional_link_attrs) { { expires_at: expiration_date } }
it 'clears expiry date' do it 'clears expiry date' do
page.within find_group_row(group) do page.within find_group_row(group) do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
find('[data-testid="clear-button"]').click find('[data-testid="clear-button"]').click
wait_for_requests wait_for_requests
expect(page).to have_content('No expiration set') expect(page).to have_field('Expiration date', with: '')
end end
end end
end end
......
...@@ -165,6 +165,8 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -165,6 +165,8 @@ RSpec.describe 'Project > Members > Invite group', :js do
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:group) { create(:group) } let!(:group) { create(:group) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
around do |example| around do |example|
freeze_time { example.run } freeze_time { example.run }
end end
...@@ -176,15 +178,14 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -176,15 +178,14 @@ RSpec.describe 'Project > Members > Invite group', :js do
visit project_project_members_path(project) visit project_project_members_path(project)
invite_group(group.name, role: 'Guest', expires_at: 5.days.from_now) invite_group(group.name, role: 'Guest', expires_at: expiration_date)
end end
it 'the group link shows the expiration time with a warning class' do it 'the group link shows the expiration time with a warning class' do
setup setup
click_link 'Groups' click_link 'Groups'
expect(find_group_row(group)).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: expiration_date)
expect(find_group_row(group)).to have_selector('.gl-text-orange-500')
end end
end end
......
...@@ -9,6 +9,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date ...@@ -9,6 +9,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
let_it_be(:maintainer) { create(:user) } let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:three_days_from_now) { 3.days.from_now.to_date }
let_it_be(:five_days_from_now) { 5.days.from_now.to_date }
let(:new_member) { create(:user) } let(:new_member) { create(:user) }
...@@ -22,39 +24,39 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date ...@@ -22,39 +24,39 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
it 'expiration date is displayed in the members list' do it 'expiration date is displayed in the members list' do
visit project_project_members_path(project) visit project_project_members_path(project)
invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date) invite_member(new_member.name, role: 'Guest', expires_at: five_days_from_now)
page.within find_member_row(new_member) do page.within find_member_row(new_member) do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: five_days_from_now)
end end
end end
it 'changes expiration date' do it 'changes expiration date' do
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date) project.team.add_users([new_member.id], :developer, expires_at: three_days_from_now)
visit project_project_members_path(project) visit project_project_members_path(project)
page.within find_member_row(new_member) do page.within find_member_row(new_member) do
fill_in 'Expiration date', with: 5.days.from_now.to_date fill_in 'Expiration date', with: five_days_from_now
find_field('Expiration date').native.send_keys :enter find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: five_days_from_now)
end end
end end
it 'clears expiration date' do it 'clears expiration date' do
project.team.add_users([new_member.id], :developer, expires_at: 5.days.from_now.to_date) project.team.add_users([new_member.id], :developer, expires_at: five_days_from_now)
visit project_project_members_path(project) visit project_project_members_path(project)
page.within find_member_row(new_member) do page.within find_member_row(new_member) do
expect(page).to have_content(/in \d days/) expect(page).to have_field('Expiration date', with: five_days_from_now)
find('[data-testid="clear-button"]').click find('[data-testid="clear-button"]').click
wait_for_requests wait_for_requests
expect(page).to have_content('No expiration set') expect(page).to have_field('Expiration date', with: '')
end end
end end
......
import { within } from '@testing-library/dom';
import { mount, createWrapper } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ExpiresAt from '~/members/components/table/expires_at.vue';
describe('ExpiresAt', () => {
// March 15th, 2020
useFakeDate(2020, 2, 15);
let wrapper;
const createComponent = (propsData) => {
wrapper = mount(ExpiresAt, {
propsData,
directives: {
GlTooltip: createMockDirective(),
},
});
};
const getByText = (text, options) =>
createWrapper(within(wrapper.element).getByText(text, options));
const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
afterEach(() => {
wrapper.destroy();
});
describe('when no expiration date is set', () => {
it('displays "No expiration set"', () => {
createComponent({ date: null });
expect(getByText('No expiration set').exists()).toBe(true);
});
});
describe('when expiration date is in the past', () => {
let expiredText;
beforeEach(() => {
createComponent({ date: '2019-03-15T00:00:00.000' });
expiredText = getByText('Expired');
});
it('displays "Expired"', () => {
expect(expiredText.exists()).toBe(true);
expect(expiredText.classes()).toContain('gl-text-red-500');
});
it('displays tooltip with formatted date', () => {
const tooltipDirective = getTooltipDirective(expiredText);
expect(tooltipDirective).not.toBeUndefined();
expect(expiredText.attributes('title')).toBe('Mar 15, 2019 12:00am UTC');
});
});
describe('when expiration date is in the future', () => {
it.each`
date | expected | warningColor
${'2020-03-23T00:00:00.000'} | ${'in 8 days'} | ${false}
${'2020-03-20T00:00:00.000'} | ${'in 5 days'} | ${true}
${'2020-03-16T00:00:00.000'} | ${'in 1 day'} | ${true}
${'2020-03-15T05:00:00.000'} | ${'in about 5 hours'} | ${true}
${'2020-03-15T01:00:00.000'} | ${'in about 1 hour'} | ${true}
${'2020-03-15T00:30:00.000'} | ${'in 30 minutes'} | ${true}
${'2020-03-15T00:01:15.000'} | ${'in 1 minute'} | ${true}
${'2020-03-15T00:00:15.000'} | ${'in less than a minute'} | ${true}
`('displays "$expected"', ({ date, expected, warningColor }) => {
createComponent({ date });
const expiredText = getByText(expected);
expect(expiredText.exists()).toBe(true);
if (warningColor) {
expect(expiredText.classes()).toContain('gl-text-orange-500');
} else {
expect(expiredText.classes()).not.toContain('gl-text-orange-500');
}
});
});
});
...@@ -10,7 +10,6 @@ import setWindowLocation from 'helpers/set_window_location_helper'; ...@@ -10,7 +10,6 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CreatedAt from '~/members/components/table/created_at.vue'; import CreatedAt from '~/members/components/table/created_at.vue';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue'; import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
import ExpiresAt from '~/members/components/table/expires_at.vue';
import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
import MemberAvatar from '~/members/components/table/member_avatar.vue'; import MemberAvatar from '~/members/components/table/member_avatar.vue';
import MemberSource from '~/members/components/table/member_source.vue'; import MemberSource from '~/members/components/table/member_source.vue';
...@@ -68,7 +67,6 @@ describe('MembersTable', () => { ...@@ -68,7 +67,6 @@ describe('MembersTable', () => {
stubs: [ stubs: [
'member-avatar', 'member-avatar',
'member-source', 'member-source',
'expires-at',
'created-at', 'created-at',
'member-action-buttons', 'member-action-buttons',
'role-dropdown', 'role-dropdown',
...@@ -119,7 +117,6 @@ describe('MembersTable', () => { ...@@ -119,7 +117,6 @@ describe('MembersTable', () => {
${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt} ${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt}
${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt} ${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt}
${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt} ${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt}
${'expires'} | ${'Access expires'} | ${memberMock} | ${ExpiresAt}
${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown} ${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown}
${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker} ${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker}
`('renders the $label field', ({ field, label, member, expectedComponent }) => { `('renders the $label field', ({ field, label, member, expectedComponent }) => {
......
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