Skip to content

Commit db891e6

Browse files
brianjgeigerfabmiz
andauthored
[ENG-2467 ] Allow contributors to remove themselves via readonly contributor card (#1131)
* Allow read+write contributors to remove themselves via readonly contributor card * Remove unnecessary braces Co-authored-by: Fabrice Mizero <fabrice@cos.io> * Better use of test selectors Co-authored-by: Fabrice Mizero <fabrice@cos.io> * Make CR changes * Add acceptance tests Co-authored-by: Fabrice Mizero <fabrice@cos.io>
1 parent 3433ed7 commit db891e6

File tree

9 files changed

+185
-16
lines changed

9 files changed

+185
-16
lines changed

lib/osf-components/addon/components/contributors/card/readonly/component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { tagName } from '@ember-decorators/component';
22
import Component from '@ember/component';
3+
import { inject as service } from '@ember/service';
34

45
import { action } from '@ember/object';
56
import { tracked } from '@glimmer/tracking';
67
import { layout } from 'ember-osf-web/decorators/component';
78
import ContributorModel from 'ember-osf-web/models/contributor';
9+
import CurrentUser from 'ember-osf-web/services/current-user';
810
import styles from './styles';
911
import template from './template';
1012

@@ -13,6 +15,7 @@ import template from './template';
1315
export default class ContributorsCardReadonly extends Component {
1416
// required
1517
contributor!: ContributorModel;
18+
@service currentUser!: CurrentUser;
1619

1720
@tracked showDropdown = false;
1821

lib/osf-components/addon/components/contributors/card/readonly/styles.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88

99
.CardSection {
1010
composes: CardSection from '../styles.scss';
11+
max-width: 34%;
12+
}
13+
14+
.CardSectionSmall {
15+
composes: CardSection from '../styles.scss';
16+
max-width: 16%;
17+
}
18+
19+
.CardSectionXS {
20+
composes: CardSection from '../styles.scss';
21+
max-width: 8%;
1122
}
1223

1324
.DropdownSection {

lib/osf-components/addon/components/contributors/card/readonly/template.hbs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div
2-
data-test-contributor-card
2+
data-test-contributor-card={{@contributor.id}}
33
data-analytics-scope='Contributor card'
44
local-class='CardContainer'
55
>
@@ -34,22 +34,40 @@
3434
</span>
3535
<span
3636
data-test-contributor-citation={{@contributor.id}}
37-
local-class='CardSection'
37+
local-class='CardSectionSmall'
3838
>
3939
{{t (concat 'osf-components.contributors.citation.' @contributor.bibliographic)}}
4040
</span>
4141
<span
4242
data-test-contributor-caret={{@contributor.id}}
43-
data-analytics-name='{{if this.showDropdown 'Expand' 'Collapse'}} employment and education info'
44-
local-class='CardSection'
43+
data-analytics-name='{{if this.showDropdown 'Collapse' 'Expand'}} employment and education info'
44+
aria-label={{if this.showDropdown
45+
(t 'osf-components.contributors.educationAndEmployment.collapse')
46+
(t 'osf-components.contributors.educationAndEmployment.expand')
47+
}}
48+
local-class='CardSectionXS'
4549
role='button'
4650
{{on 'click' this.toggleDropdown}}
4751
>
4852
<FaIcon
4953
@icon={{if this.showDropdown 'caret-up' 'caret-down'}}
5054
/>
5155
</span>
56+
<span local-class='CardSectionXS'>
57+
{{#if (eq @contributor.users.id this.currentUser.user.id)}}
58+
<DeleteButton
59+
data-test-contributor-remove-self={{@contributor.id}}
60+
@small={{true}}
61+
@noBackground={{true}}
62+
@delete={{fn @manager.removeContributor @contributor}}
63+
@modalTitle={{t 'contributor_list.remove_contributor.confirm_remove.title'}}
64+
@modalBody={{t 'contributor_list.remove_contributor.confirm_remove.body'}}
65+
@confirmButtonText={{t 'contributor_list.remove_contributor.confirm_remove.button'}}
66+
/>
67+
{{/if}}
68+
</span>
5269
</div>
70+
</div>
5371
{{#if this.showDropdown}}
5472
<div data-test-contributor-card-dropdown local-class='DropdownSection'>
5573
<ul local-class='DropdownList'>
@@ -86,4 +104,3 @@
86104
</ul>
87105
</div>
88106
{{/if}}
89-
</div>

lib/osf-components/addon/components/contributors/list/template.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{#let (get
22
(hash
3-
readonly=(component 'contributors/card/readonly')
3+
readonly=(component 'contributors/card/readonly' manager=@contributorsManager)
44
editable=(component 'contributors/card/editable' manager=@contributorsManager)
55
)
66
@widgetMode

lib/osf-components/addon/components/contributors/manager/component.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { tagName } from '@ember-decorators/component';
22
import Component from '@ember/component';
33
import { computed } from '@ember/object';
4+
import RouterService from '@ember/routing/router-service';
45
import { inject as service } from '@ember/service';
56
import { tracked } from '@glimmer/tracking';
67
import { task } from 'ember-concurrency-decorators';
@@ -10,15 +11,18 @@ import ContributorModel from 'ember-osf-web/models/contributor';
1011
import DraftRegistrationModel from 'ember-osf-web/models/draft-registration';
1112
import NodeModel from 'ember-osf-web/models/node';
1213
import { Permission } from 'ember-osf-web/models/osf-model';
14+
import CurrentUser from 'ember-osf-web/services/current-user';
1315
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
1416
import Toast from 'ember-toastr/services/toast';
1517
import template from './template';
1618

1719
@layout(template)
1820
@tagName('')
1921
export default class ContributorsManager extends Component {
22+
@service currentUser!: CurrentUser;
2023
@service toast!: Toast;
2124
@service intl!: Intl;
25+
@service router!: RouterService;
2226
node?: NodeModel | DraftRegistrationModel;
2327
@tracked contributors: ContributorModel[] = [];
2428
@tracked totalPage: number = 1;
@@ -103,16 +107,23 @@ export default class ContributorsManager extends Component {
103107
@task({ withTestWaiter: true, enqueue: true })
104108
removeContributor = task(
105109
function *(this: ContributorsManager, contributor: ContributorModel) {
110+
const user = this.currentUser.get('user');
106111
try {
107112
yield contributor.destroyRecord();
108113
this.contributors.removeObject(contributor);
109-
const contributorName = contributor.unregisteredContributor
110-
? contributor.unregisteredContributor
111-
: contributor.users.get('fullName');
112-
this.toast.success(this.intl.t(
113-
'osf-components.contributors.removeContributor.success',
114-
{ contributorName, htmlSafe: true },
115-
));
114+
115+
if (user && user.id === contributor.users.get('id')) {
116+
this.toast.success(this.intl.t('contributor_list.remove_contributor.success'));
117+
this.router.transitionTo('home');
118+
} else {
119+
const contributorName = contributor.unregisteredContributor
120+
? contributor.unregisteredContributor
121+
: contributor.users.get('fullName');
122+
this.toast.success(this.intl.t(
123+
'osf-components.contributors.removeContributor.success',
124+
{ contributorName, htmlSafe: true },
125+
));
126+
}
116127
} catch (e) {
117128
const apiError = getApiErrorMessage(e);
118129
const errorHeading = this.intl.t('osf-components.contributors.removeContributor.errorHeading');

mirage/scenarios/default.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,18 @@ function registrationScenario(
7272

7373
const rootNode = server.create('node', {
7474
public: false,
75-
contributors: server.createList('contributor', 11),
75+
contributors: server.createList('contributor', 10),
7676
currentUserPermissions: [Permission.Admin],
7777
}, 'withFiles', 'withStorage');
7878
rootNode.update({
7979
storage: server.create('node-storage', { storageLimitStatus: StorageStatus.OVER_PRIVATE }),
8080
});
81+
server.create('contributor', {
82+
node: rootNode,
83+
users: currentUser,
84+
permission: Permission.Admin,
85+
index: 0,
86+
});
8187

8288
const childNodeA = server.create('node', { parent: rootNode });
8389
server.create('node', { parent: childNodeA });

tests/engines/registries/acceptance/draft/draft-test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { setBreakpoint } from 'ember-responsive/test-support';
1515
import { TestContext } from 'ember-test-helpers';
1616
import { module, test } from 'qunit';
1717

18+
import { Permission } from 'ember-osf-web/models/osf-model';
1819
import { visit } from 'ember-osf-web/tests/helpers';
1920
import { setupEngineApplicationTest } from 'ember-osf-web/tests/helpers/engines';
2021
import { deserializeResponseKey } from 'ember-osf-web/transforms/registration-response-key';
@@ -397,7 +398,73 @@ module('Registries | Acceptance | draft form', hooks => {
397398
assert.equal(currentURL(), '/dashboard', 'user is redirected to /dashboard');
398399
});
399400

400-
test('removeMe fails', async assert => {
401+
test('metadata: contributor can remove herself', async assert => {
402+
const currentUser = server.create('user', 'loggedIn');
403+
const registrationSchema = server.schema.registrationSchemas.find('testSchema');
404+
const branchedFrom = server.create('node');
405+
const thisContributor = server.create('contributor', {
406+
users: currentUser,
407+
index: 0,
408+
node: branchedFrom,
409+
permission: Permission.Write,
410+
});
411+
server.createList('contributor', 1, { node: branchedFrom });
412+
const draftRegistration = server.create('draft-registration',
413+
{ registrationSchema, initiator: currentUser, branchedFrom });
414+
415+
await visit(`/registries/drafts/${draftRegistration.id}/metadata`);
416+
417+
assert.dom(`[data-test-contributor-remove-self="${thisContributor.id}"] > button`)
418+
.isVisible('remove me button is visible');
419+
await click(`[data-test-contributor-remove-self="${thisContributor.id}"] > button`);
420+
421+
assert.dom('.modal-content').isVisible('removeMe hard-confirm modal is visible');
422+
assert.dom('[data-test-confirm-delete]').isVisible('removeMe hard-confirm modal has confirm button');
423+
await percySnapshot(assert);
424+
425+
await click('[data-test-confirm-delete]');
426+
await settled();
427+
assert.equal(currentURL(), '/dashboard', 'user is redirected to /dashboard');
428+
});
429+
430+
test('metadata: removeMe fails', async assert => {
431+
const currentUser = server.create('user', 'loggedIn');
432+
const registrationSchema = server.schema.registrationSchemas.find('testSchema');
433+
const branchedFrom = server.create('node');
434+
const thisContributor = server.create('contributor', {
435+
users: currentUser,
436+
index: 0,
437+
node: branchedFrom,
438+
permission: Permission.Write,
439+
});
440+
server.createList('contributor', 1, { node: branchedFrom });
441+
const draftRegistration = server.create('draft-registration',
442+
{ registrationSchema, initiator: currentUser, branchedFrom });
443+
444+
await visit(`/registries/drafts/${draftRegistration.id}/metadata`);
445+
446+
assert.dom(`[data-test-contributor-remove-self="${thisContributor.id}"] > button`)
447+
.isVisible('remove me button is visible');
448+
await click(`[data-test-contributor-remove-self="${thisContributor.id}"] > button`);
449+
450+
assert.dom('.modal-content').isVisible('removeMe hard-confirm modal is visible');
451+
assert.dom('[data-test-confirm-delete]').isVisible('removeMe hard-confirm modal has confirm button');
452+
453+
server.namespace = '/v2';
454+
server.del('/nodes/:parentID/contributors/:id', () => ({
455+
errors: [{ detail: 'Error occured' }],
456+
}), 400);
457+
458+
await click('[data-test-confirm-delete]');
459+
const errorHeading = t('osf-components.contributors.removeContributor.errorHeading');
460+
const errorMessage = `${errorHeading}Error occured`;
461+
assert.dom('#toast-container', document as unknown as Element).hasTextContaining(
462+
stripHtmlTags(errorMessage.toString()),
463+
'Toast error message shows; has the right text',
464+
);
465+
});
466+
467+
test('review: removeMe fails', async assert => {
401468
const currentUser = server.create('user', 'loggedIn');
402469
const registrationSchema = server.schema.registrationSchemas.find('testSchema');
403470
const branchedFrom = server.create('node');

tests/integration/components/contributors/component-test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import a11yAudit from 'ember-a11y-testing/test-support/audit';
33
import { setupMirage } from 'ember-cli-mirage/test-support';
44
import { setupIntl, t } from 'ember-intl/test-support';
55
import { Permission } from 'ember-osf-web/models/osf-model';
6+
import CurrentUser from 'ember-osf-web/services/current-user';
67
import { selectChoose } from 'ember-power-select/test-support';
78
import { clickTrigger } from 'ember-power-select/test-support/helpers';
89
import { setupRenderingTest } from 'ember-qunit';
@@ -12,13 +13,18 @@ import hbs from 'htmlbars-inline-precompile';
1213
import { module, skip, test } from 'qunit';
1314
import { OsfLinkRouterStub } from '../../helpers/osf-link-router-stub';
1415

16+
interface ThisTestContext extends TestContext {
17+
currentUser: CurrentUser;
18+
}
19+
1520
module('Integration | Component | contributors', hooks => {
1621
setupRenderingTest(hooks);
1722
setupMirage(hooks);
1823
setupIntl(hooks);
1924

20-
hooks.beforeEach(function(this: TestContext) {
25+
hooks.beforeEach(function(this: ThisTestContext) {
2126
this.store = this.owner.lookup('service:store');
27+
this.currentUser = this.owner.lookup('service:current-user');
2228
this.owner.register('service:router', OsfLinkRouterStub);
2329
server.loadFixtures('schema-blocks');
2430
server.loadFixtures('registration-schemas');
@@ -96,6 +102,51 @@ module('Integration | Component | contributors', hooks => {
96102
assert.ok(true, 'No a11y errors on page');
97103
});
98104

105+
test('read-only contributor card can remove self as contributor', async function(assert) {
106+
const currentUser = server.create('user', { id: 'sprout' });
107+
const currentUserModel = await this.store.findRecord('user', 'sprout');
108+
this.owner.lookup('service:current-user').setProperties({
109+
user: currentUserModel, currentUserId: currentUserModel.id,
110+
});
111+
112+
const firstContributor = server.create('contributor', {
113+
fullName: 'First Contributor',
114+
index: 0,
115+
id: 'Remove',
116+
users: currentUser,
117+
});
118+
const secondContributor = server.create('contributor', {
119+
fullName: 'Second Contributor',
120+
index: 1,
121+
id: 'Keep',
122+
});
123+
const draftRegistration = server.create('draft-registration', {
124+
contributors: [firstContributor, secondContributor],
125+
});
126+
const registrationModel = await this.store.findRecord('draft-registration', draftRegistration.id);
127+
this.set('node', registrationModel);
128+
await render(hbs`<Contributors::Widget @node={{this.node}} @widgetMode='readonly' />`);
129+
assert.dom('[data-test-contributor-card="Keep"]').isVisible(
130+
'"Keep" card is visible before contributor removal',
131+
);
132+
assert.dom('[data-test-contributor-card="Remove"]').isVisible(
133+
'"Remove" card is visible before contributor removal',
134+
);
135+
assert.dom('[data-test-contributor-remove-self="Remove"]')
136+
.exists('There is a delete button for currentUser contributor');
137+
assert.dom('[data-test-contributor-remove-self="Keep"]')
138+
.doesNotExist('There is no delete button for non-currentUser contributor');
139+
await click('[data-test-contributor-remove-self="Remove"] > button');
140+
await click('[data-test-confirm-delete]');
141+
142+
assert.dom('[data-test-contributor-card="Keep"]').isVisible(
143+
'"Keep" card is visible after contributor removal',
144+
);
145+
assert.dom('[data-test-contributor-card="Remove"]').isNotVisible(
146+
'"Remove" card is not visible after contributor removal',
147+
);
148+
});
149+
99150
test('editable user card renders', async function(assert) {
100151
const draftRegistration = server.create('draft-registration');
101152
const contributor = server.create('contributor', {

translations/en-us.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,9 @@ osf-components:
16241624
success: 'Contributor order updated.'
16251625
noEducation: 'No education history to show'
16261626
noEmployment: 'No employment history to show'
1627+
educationAndEmployment:
1628+
expand: 'Expand employment and education info'
1629+
collapse: 'Collapse employment and education info'
16271630
removeContributor:
16281631
confirmRemove:
16291632
title: 'Remove contributor'

0 commit comments

Comments
 (0)