Skip to content

Commit da93274

Browse files
committed
Merge branch 'release/23.01.0'
2 parents 043629d + e04c1a7 commit da93274

File tree

41 files changed

+1173
-608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1173
-608
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [23.01.0] - 2023-01-25
8+
### Changed
9+
- Collections Moderation bug and improvements
10+
### Added
11+
- User profile settings page institutional affiliation management
712

813
## [22.11.0] - 2022-12-19
914
### Changed
@@ -1884,6 +1889,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
18841889
### Added
18851890
- Quick Files
18861891

1892+
[23.01.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/23.01.0
1893+
[22.11.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.11.0
1894+
[22.10.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.10.0
1895+
[22.9.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.9.0
18871896
[22.8.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.8.0
18881897
[22.7.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.7.0
18891898
[22.6.0]: https://github.com/CenterForOpenScience/ember-osf-web/releases/tag/22.6.0

app/meetings/detail/-components/meeting-detail-header/template.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
<ul data-test-meeting-email-address-line>
5656
{{#if this.meeting.isAcceptingTypeOne}}
5757
<li>
58-
{{t 'meetings.detail.meeting-detail-header.email_text'
58+
{{t 'meetings.detail.meeting-detail-header.email_text_mail_to'
5959
typeName=this.meeting.fieldNames.submission1_plural
6060
emailAddress=this.meeting.typeOneSubmissionEmail
6161
htmlSafe=true
@@ -65,7 +65,7 @@
6565

6666
{{#if this.meeting.isAcceptingTypeTwo}}
6767
<li>
68-
{{t 'meetings.detail.meeting-detail-header.email_text'
68+
{{t 'meetings.detail.meeting-detail-header.email_text_mail_to'
6969
typeName=this.meeting.fieldNames.submission2_plural
7070
emailAddress=this.meeting.typeTwoSubmissionEmail
7171
htmlSafe=true

app/packages/registration-schema/validations.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { SchemaBlockGroup } from 'ember-osf-web/packages/registration-schema/sch
1212
import { validateFileList } from 'ember-osf-web/validators/validate-response-format';
1313
import SchemaResponseModel from 'ember-osf-web/models/schema-response';
1414

15+
type LicensedContent = DraftRegistration | NodeModel;
16+
1517
export const NodeLicenseFields: Record<keyof NodeLicense, string> = {
1618
copyrightHolders: 'Copyright Holders',
1719
year: 'Year',
@@ -78,7 +80,7 @@ export function buildValidation(groups: SchemaBlockGroup[], node?: NodeModel | D
7880
}
7981

8082
export function validateNodeLicense() {
81-
return async (_: unknown, __: unknown, ___: unknown, changes: DraftRegistration, content: DraftRegistration) => {
83+
return async (_: unknown, __: unknown, ___: unknown, changes: LicensedContent, content: LicensedContent) => {
8284
let validateLicenseTarget = await content.license;
8385
let validateNodeLicenseTarget = content.nodeLicense;
8486
if (changes?.license && Object.keys(changes.license).length
@@ -93,6 +95,19 @@ export function validateNodeLicense() {
9395
if (!validateLicenseTarget || validateLicenseTarget?.requiredFields?.length === 0) {
9496
return true;
9597
}
98+
99+
if (validateLicenseTarget.requiredFields?.includes('year')) {
100+
const year = validateNodeLicenseTarget?.year;
101+
const regex = /^((?!(0))[0-9]{4})$/;
102+
if (year && !regex.test(year)) {
103+
return {
104+
context: {
105+
type: 'year_format',
106+
},
107+
};
108+
}
109+
}
110+
96111
const missingFieldsList: Array<keyof NodeLicense> = [];
97112
for (const item of validateLicenseTarget.requiredFields) {
98113
if (!validateNodeLicenseTarget || !validateNodeLicenseTarget[item]) {
@@ -115,27 +130,6 @@ export function validateNodeLicense() {
115130
};
116131
}
117132

118-
export function validateNodeLicenseYear() {
119-
return (_: unknown, __: unknown, ___: unknown, changes: any, content: DraftRegistration) => {
120-
let validateYearTarget;
121-
if (content.nodeLicense && 'year' in content.nodeLicense) {
122-
validateYearTarget = content.nodeLicense.year;
123-
}
124-
if (changes?.nodeLicense && 'year' in changes.nodeLicense) {
125-
validateYearTarget = changes.nodeLicense.year;
126-
}
127-
const regex = /^((?!(0))[0-9]{4})$/;
128-
if (typeof validateYearTarget !== 'undefined' && !regex.test(validateYearTarget)) {
129-
return {
130-
context: {
131-
type: 'year_format',
132-
},
133-
};
134-
}
135-
return true;
136-
};
137-
}
138-
139133
export function validateSubjects() {
140134
return (_: unknown, __: unknown, ___: unknown, ____: unknown, content: DraftRegistration) => {
141135
const subjects = content.hasMany('subjects').value();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Component from '@glimmer/component';
2+
import { inject as service } from '@ember/service';
3+
import CurrentUserService from 'ember-osf-web/services/current-user';
4+
import { task } from 'ember-concurrency';
5+
import { waitFor } from '@ember/test-waiters';
6+
import { taskFor } from 'ember-concurrency-ts';
7+
import InstitutionModel from 'ember-osf-web/models/institution';
8+
import config from 'ember-get-config';
9+
import IntlService from 'ember-intl/services/intl';
10+
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
11+
12+
const { support: { supportEmail } } = config;
13+
14+
export default class AffiliatedInstitutionsComponent extends Component {
15+
@service currentUser!: CurrentUserService;
16+
@service intl!: IntlService;
17+
@service toast!: Toastr;
18+
19+
reloadAffiliations: any;
20+
21+
@task
22+
@waitFor
23+
async removeAffiliationTask(institution: InstitutionModel) {
24+
try {
25+
await this.currentUser.user?.deleteM2MRelationship('institutions', institution);
26+
} catch (e) {
27+
const errorMessage = this.intl.t(
28+
'settings.account.connected_identities.remove_fail',
29+
{ supportEmail, htmlSafe: true },
30+
);
31+
captureException(e, { errorMessage: errorMessage.toString() });
32+
this.toast.error(getApiErrorMessage(e), errorMessage as string);
33+
}
34+
this.reloadAffiliations();
35+
}
36+
37+
removeAffiliation(institution: InstitutionModel) {
38+
taskFor(this.removeAffiliationTask).perform(institution);
39+
}
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.institutions-list ul {
2+
margin: 0;
3+
4+
& > li {
5+
border: 0;
6+
padding: 10px;
7+
8+
&:hover {
9+
background-color: #e6e6e6;
10+
}
11+
}
12+
}
13+
14+
.delete-button {
15+
margin-top: -2px;
16+
float: right;
17+
clear: right;
18+
19+
& > button {
20+
padding: 0;
21+
}
22+
}
23+
24+
.description-list-separator {
25+
margin-bottom: 10px;
26+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Panel
2+
data-test-connected-identities-panel
3+
data-analytics-scope='Connected identities panel'
4+
as |panel|
5+
>
6+
<panel.heading @title={{t 'settings.account.affiliatedInstitutions.affiliatedInstitutions'}} />
7+
<panel.body>
8+
<p data-test-affiliated-institutions-help-text>
9+
{{t 'settings.account.affiliatedInstitutions.helpText' htmlSafe=true}}
10+
</p>
11+
<hr local-class='description-list-separator'>
12+
<PaginatedList::HasMany
13+
data-analytics-scope='User Affiliated Institutions'
14+
local-class='institutions-list'
15+
@model={{this.currentUser.user}}
16+
@relationshipName='institutions'
17+
@bindReload={{action (mut this.reloadAffiliations)}}
18+
as |list|
19+
>
20+
<list.item as |item|>
21+
<span data-test-affiliated-institutions-item='{{item.id}}'>
22+
{{item.name}}
23+
<DeleteButton
24+
data-test-affiliated-institutions-delete
25+
local-class='delete-button'
26+
@small={{true}}
27+
@noBackground={{true}}
28+
@delete={{action this.removeAffiliation item}}
29+
@modalTitle={{t 'settings.account.affiliatedInstitutions.deleteModalTitle'}}
30+
@modalBody={{t 'settings.account.affiliatedInstitutions.deleteModalBody' institutionName=item.name htmlSafe=true}}
31+
/>
32+
</span>
33+
</list.item>
34+
35+
<list.empty>
36+
{{t 'settings.account.affiliatedInstitutions.noAffiliations'}}
37+
</list.empty>
38+
</PaginatedList::HasMany>
39+
</panel.body>
40+
</Panel>

app/settings/account/template.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<Settings::Account::-Components::ConnectedEmails />
33
<Settings::Account::-Components::DefaultRegion />
44
<Settings::Account::-Components::ConnectedIdentities />
5+
<Settings::Account::-Components::AffiliatedInstitutions />
56
<Settings::Account::-Components::ChangePassword />
67
<Settings::Account::-Components::Security />
78
<Settings::Account::-Components::RequestDeactivation />

lib/app-components/addon/components/branded-navbar/template.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
{{#if @displayModerationButton}}
3535
<li>
3636
<OsfLink
37+
data-analytics-name='Collection moderation'
38+
data-test-branded-navbar-moderation
3739
@route='collections.provider.moderation'
3840
@models={{array this.providerId}}
3941
>

lib/app-components/addon/components/project-metadata/component.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import Component from '@ember/component';
44
import { action } from '@ember/object';
55
import { inject as service } from '@ember/service';
66
import { waitFor } from '@ember/test-waiters';
7+
import { validatePresence } from 'ember-changeset-validations/validators';
78
import { task } from 'ember-concurrency';
89
import Intl from 'ember-intl/services/intl';
910
import Toast from 'ember-toastr/services/toast';
11+
import { BufferedChangeset } from 'validated-changeset';
1012

1113
import { layout, requiredAction } from 'ember-osf-web/decorators/component';
14+
import CollectionProviderModel from 'ember-osf-web/models/collection-provider';
15+
import LicenseModel from 'ember-osf-web/models/license';
1216
import Node from 'ember-osf-web/models/node';
17+
import { validateNodeLicense } from 'ember-osf-web/packages/registration-schema/validations';
1318
import Analytics from 'ember-osf-web/services/analytics';
19+
import buildChangeset from 'ember-osf-web/utils/build-changeset';
20+
import captureException from 'ember-osf-web/utils/capture-exception';
1421
import styles from './styles';
1522
import template from './template';
1623

@@ -22,17 +29,88 @@ export default class ProjectMetadata extends Component {
2229
@service store!: Store;
2330
@service toast!: Toast;
2431

32+
provider!: CollectionProviderModel;
2533
node!: Node;
34+
changeset!: BufferedChangeset;
35+
36+
nodeValidations = {
37+
title: [
38+
validatePresence({ presence: true, ignoreBlank: true, type: 'empty' }),
39+
],
40+
description: [
41+
validatePresence({ presence: true, ignoreBlank: true, type: 'empty' }),
42+
],
43+
license: [
44+
this.validateCollectionLicense(),
45+
],
46+
nodeLicense: [
47+
validateNodeLicense(),
48+
],
49+
};
2650

2751
@requiredAction continue!: () => void;
2852

53+
init() {
54+
super.init();
55+
this.changeset = buildChangeset(this.node, this.nodeValidations);
56+
}
57+
2958
@task
3059
@waitFor
3160
async reset() {
3261
this.node.rollbackAttributes();
3362
await this.node.reload();
3463
}
3564

65+
@task
66+
@waitFor
67+
async save() {
68+
await this.changeset.validate();
69+
if (this.changeset.isValid) {
70+
try {
71+
await this.changeset.save();
72+
this.onSave();
73+
} catch (e) {
74+
this.onError(e);
75+
}
76+
} else {
77+
this.toast.error(this.intl.t('app_components.project_metadata.invalid_metadata'));
78+
}
79+
}
80+
81+
validateCollectionLicense() {
82+
return async (_: unknown, newValue: LicenseModel, oldValue: Promise<LicenseModel>, changes: Partial<Node>) => {
83+
// if the license has not changed, use the old value to validate
84+
// changes.license may exist even if the license has not changed
85+
let currentLicense = newValue;
86+
if (!changes.license?.id) {
87+
currentLicense = await oldValue;
88+
}
89+
if (!currentLicense) {
90+
return {
91+
context: {
92+
type: 'mustSelect',
93+
},
94+
};
95+
}
96+
97+
const licensesAcceptable = await this.provider.queryHasMany('licensesAcceptable', {
98+
filter: {
99+
name: currentLicense.name,
100+
},
101+
});
102+
103+
if (!licensesAcceptable.includes(currentLicense)) {
104+
return {
105+
context: {
106+
type: 'license_not_accepted',
107+
},
108+
};
109+
}
110+
return true;
111+
};
112+
}
113+
36114
@action
37115
addTag(tag: string) {
38116
this.analytics.click('button', 'Collection - Submit - Add tag');
@@ -52,7 +130,8 @@ export default class ProjectMetadata extends Component {
52130
}
53131

54132
@action
55-
onError() {
133+
onError(e: Error) {
134+
captureException(e);
56135
this.toast.error(this.intl.t('app_components.project_metadata.save_error'));
57136
}
58137
}

lib/app-components/addon/components/project-metadata/template.hbs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
<ValidatedModelForm
2-
@onSave={{action this.onSave}}
3-
@onError={{action this.onError}}
4-
@model={{this.node}}
1+
<FormControls
2+
@changeset={{this.changeset}}
53
as |form|
64
>
75
<div class='col-md-6'>
@@ -73,10 +71,11 @@
7371
data-test-project-metadata-save-button
7472
data-analytics-name='Save metadata'
7573
disabled={{form.submitting}}
74+
@onClick={{perform this.save}}
7675
@buttonType='submit'
7776
@type='primary'
7877
>
7978
{{t 'app_components.submit_section.save'}}
8079
</BsButton>
8180
</div>
82-
</ValidatedModelForm>
81+
</FormControls>

0 commit comments

Comments
 (0)