Skip to content

Commit 68127ba

Browse files
authored
Merge pull request #639 from CenterForOpenScience/release/carbon
Carbon! AKA Remainder of the Registries overview page
2 parents e7831da + bc8b75c commit 68127ba

File tree

131 files changed

+2581
-899
lines changed

Some content is hidden

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

131 files changed

+2581
-899
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ 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

77
## [Unreleased]
8+
### Added
9+
- Features:
10+
- Registries overview navigation menu (#600)
11+
- Editable registration institutions (#617)
12+
- Display registration wiki count (#625)
13+
- Add `citation_doi` to `<meta>` tags (#628)
14+
- Components:
15+
- `citation-viewer` - displays citations for a node (#608)
16+
- Data:
17+
- `Node.bibliographicContributors` relationship (#604)
18+
- `OsfModel.sparseHasMany`, `sparseLoadAll` (#614)
19+
- Utils:
20+
- Sparse fieldset utils (#614)
21+
22+
### Changed
23+
- Components:
24+
- `contributor-list` - display only bibliographic contributors (#604)
25+
26+
### Fixed
27+
- Registries discover page - recognize links to registrations on test.osf.io (#597)
28+
- Registration form rendering errors (#620)
29+
- Allow withdrawing registrations without justification (#622)
30+
- Position tooltips and footer correctly (#624, #626)
831

932
## [19.3.0]
1033
### Added

app/adapters/citation-style.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import OsfAdapter from './osf-adapter';
2+
3+
export default class CitationStyleAdapter extends OsfAdapter {
4+
pathForType(_: string): string {
5+
return 'citations/styles';
6+
}
7+
}
8+
9+
declare module 'ember-data/types/registries/adapter' {
10+
export default interface AdapterRegistry {
11+
'citation-style': CitationStyleAdapter;
12+
} // eslint-disable-line semi
13+
}

app/application/styles.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.Application {
2-
height: 100%;
2+
min-height: 100vh;
33
display: flex;
44
flex-direction: column;
55
}

app/dashboard/controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default class Dashboard extends Controller {
7575
const user: User = yield this.currentUser.user;
7676

7777
const nodes: QueryHasManyResult<Node> = yield user.queryHasMany('nodes', {
78-
embed: ['contributors', 'parent', 'root'],
78+
embed: ['bibliographic_contributors', 'parent', 'root'],
7979
// eslint-disable-next-line ember/no-global-jquery
8080
filter: this.filter ? { title: $('<div>').text(this.filter).html() } : undefined,
8181
page: more ? this.incrementProperty('page') : this.set('page', 1),
@@ -96,7 +96,7 @@ export default class Dashboard extends Controller {
9696
try {
9797
const node: Node = yield this.store.findRecord('node', id);
9898
const linkedNodes: QueryHasManyResult<Node> = yield node.queryHasMany('linkedNodes', {
99-
embed: 'contributors',
99+
embed: 'bibliographic_contributors',
100100
page: { size: 5 },
101101
});
102102
this.set(dest, linkedNodes);

app/guid-node/forks/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default class GuidNodeForks extends Controller {
2525

2626
reloadList?: (page?: number) => void; // bound by paginated-list
2727

28-
forksQueryParams = { embed: 'contributors' };
28+
forksQueryParams = { embed: 'bibliographic_contributors' };
2929

3030
@reads('model.taskInstance.value')
3131
node?: Node;

app/guid-registration/forks/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default class GuidRegistrationForks extends Controller {
2525

2626
reloadList?: (page?: number) => void;
2727

28-
forksQueryParams = { embed: 'contributors' };
28+
forksQueryParams = { embed: 'bibliographic_contributors' };
2929

3030
@reads('model.taskInstance.value')
3131
node?: Registration;

app/locales/en/translations.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export default {
9494
required: 'Required',
9595
options: 'Options',
9696
optional: 'Optional',
97+
optional_paren: '(optional)',
9798
services: {
9899
collections: 'Collections',
99100
institutions: 'Institutions',
@@ -1077,10 +1078,15 @@ export default {
10771078
citation: 'Citation',
10781079
},
10791080

1081+
form_view: {
1082+
none_selected: 'None selected',
1083+
},
1084+
10801085
overview: {
10811086
title: 'Overview',
10821087
collapse: 'Collapse',
10831088
expand: 'Expand',
1089+
contents: 'Contents',
10841090
see_more: 'See more',
10851091
metadata: 'Metadata',
10861092
component_of: 'This is a component of a registration:',
@@ -1136,7 +1142,7 @@ export default {
11361142
},
11371143

11381144
form_view: {
1139-
no_files: 'No files uploaded',
1145+
no_files: 'No files selected',
11401146
},
11411147

11421148
withdrawn: {
@@ -1348,6 +1354,16 @@ export default {
13481354
add_institution_error: 'Error adding institution',
13491355
remove_institution_error: 'Error removing institution',
13501356
no_affiliations: 'No affiliations to add',
1357+
no_affiliated_institution: {
1358+
user: 'You have no institutional affiliations',
1359+
project: 'This project has no affiliated institutions',
1360+
registration: 'This registration has no affiliated institutions',
1361+
},
1362+
},
1363+
'citation-viewer': {
1364+
get_more: 'Get more citations',
1365+
placeholder: 'Enter citation style (e.g. "APA")',
1366+
type_to_search: 'Start typing to search citation styles',
13511367
},
13521368
},
13531369
settings: {

app/models/citation-style.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { attr } from '@ember-decorators/data';
2+
3+
import OsfModel from './osf-model';
4+
5+
export default class CitationStyleModel extends OsfModel {
6+
@attr('fixstring') title?: string;
7+
@attr('fixstring') shortTitle?: string;
8+
@attr('fixstring') summary?: string;
9+
@attr('date') dateParsed?: string;
10+
}
11+
12+
declare module 'ember-data/types/registries/model' {
13+
export default interface ModelRegistry {
14+
'citation-style': CitationStyleModel;
15+
} // eslint-disable-line semi
16+
}

app/models/node.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export default class NodeModel extends BaseFileItem.extend(Validations, Collecta
9494
@hasMany('contributor', { inverse: 'node' })
9595
contributors!: DS.PromiseManyArray<ContributorModel>;
9696

97+
@hasMany('contributor', { inverse: null })
98+
bibliographicContributors!: DS.PromiseManyArray<ContributorModel>;
99+
97100
@belongsTo('node', { inverse: 'children' })
98101
parent!: DS.PromiseObject<NodeModel> & NodeModel;
99102

app/models/osf-model.ts

Lines changed: 117 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import CurrentUser from 'ember-osf-web/services/current-user';
1515
import getHref from 'ember-osf-web/utils/get-href';
1616
import getRelatedHref from 'ember-osf-web/utils/get-related-href';
1717
import getSelfHref from 'ember-osf-web/utils/get-self-href';
18-
import pathJoin from 'ember-osf-web/utils/path-join';
18+
import {
19+
buildFieldsParam,
20+
parseSparseResource,
21+
SparseFieldset,
22+
SparseModel,
23+
} from 'ember-osf-web/utils/sparse-fieldsets';
1924

2025
import {
2126
BaseMeta,
@@ -41,6 +46,17 @@ export interface QueryHasManyResult<T> extends Array<T> {
4146
links?: Links | PaginationLinks;
4247
}
4348

49+
export interface RequestOptions {
50+
queryParams?: object;
51+
ajaxOptions?: object;
52+
}
53+
54+
export interface SparseHasManyResult {
55+
sparseModels: SparseModel[];
56+
meta: PaginatedMeta;
57+
links?: Links | PaginationLinks;
58+
}
59+
4460
export interface PaginatedQueryOptions {
4561
'page[size]': number;
4662
page: number;
@@ -70,6 +86,21 @@ export default class OsfModel extends Model {
7086
return pluralize(underscore(this.modelName));
7187
}
7288

89+
getHasManyLink<T extends OsfModel, R extends RelationshipsFor<T>>(
90+
this: T,
91+
relationshipName: R,
92+
): string {
93+
const reference = this.hasMany(relationshipName);
94+
95+
// HACK: ember-data discards/ignores the link if an object on the belongsTo side
96+
// came first. In that case, grab the link where we expect it from OSF's API
97+
const url = reference.link() || getRelatedHref(this.relationshipLinks[relationshipName]);
98+
if (!url) {
99+
throw new Error(`Could not find a link for '${relationshipName}' relationship`);
100+
}
101+
return url;
102+
}
103+
73104
/*
74105
* Query a hasMany relationship with query params
75106
*
@@ -89,18 +120,8 @@ export default class OsfModel extends Model {
89120
queryParams?: object,
90121
ajaxOptions?: object,
91122
): Promise<QueryHasManyResult<RT>> {
92-
const reference = this.hasMany(propertyName);
93-
94-
// HACK: ember-data discards/ignores the link if an object on the belongsTo side
95-
// came first. In that case, grab the link where we expect it from OSF's API
96-
const url = reference.link() || getRelatedHref(this.relationshipLinks[propertyName]);
97-
98-
if (!url) {
99-
throw new Error(`Could not find a link for '${propertyName}' relationship`);
100-
}
101-
102123
const options: object = {
103-
url,
124+
url: this.getHasManyLink(propertyName),
104125
data: queryParams,
105126
...ajaxOptions,
106127
};
@@ -186,20 +207,15 @@ export default class OsfModel extends Model {
186207
relatedModel: OsfModel,
187208
) {
188209
const apiRelationshipName = underscore(relationshipName);
189-
let url = getSelfHref(this.relationshipLinks[apiRelationshipName]);
210+
const url = getSelfHref(this.relationshipLinks[apiRelationshipName]);
190211

191-
let data = JSON.stringify({
212+
const data = JSON.stringify({
192213
data: [{
193214
id: relatedModel.id,
194215
type: relatedModel.apiType,
195216
}],
196217
});
197218

198-
if (url && action === 'delete') {
199-
data = '';
200-
url = pathJoin(url, relatedModel.id);
201-
}
202-
203219
if (!url) {
204220
throw new Error(`Couldn't find self link for ${apiRelationshipName} relationship`);
205221
}
@@ -271,4 +287,86 @@ export default class OsfModel extends Model {
271287
throw new Error(`Unexpected response ${errorContext}`);
272288
}
273289
}
290+
291+
/**
292+
* Fetch one page of a has-many relationship using sparse fieldsets.
293+
* See https://developer.osf.io/#tag/Sparse-Fieldsets
294+
*
295+
* The API response includes only the specified fields.
296+
* This is useful for potentially long lists that require rendering only a few fields.
297+
*
298+
* Does NOT use ember-data. This means a few things:
299+
* - Attributes don't pass through the transforms (e.g. 'fixstring') set with `DS.attr`, they remain as
300+
* represented in JSON. In particular, date fields are not deserialized to `Date` objects.
301+
* - Sparse models aren't put in the store. This means no potential interactions with code that does use
302+
* the store, but if you want caching you'll have to do it yourself.
303+
*
304+
* Example:
305+
* ```ts
306+
* const contributors = await node.sparseHasMany(
307+
* 'contributors',
308+
* { contributor: ['users'], user: ['fullName'] },
309+
* { queryParams: { 'page[size]': 100 } },
310+
* });
311+
*
312+
* contributors.sparseModels.forEach(contrib => {
313+
* console.log(contrib.users.fullName);
314+
* );
315+
* ```
316+
*/
317+
async sparseHasMany<T extends OsfModel>(
318+
this: T,
319+
relationshipName: RelationshipsFor<T>,
320+
fieldset: SparseFieldset,
321+
options: RequestOptions = {},
322+
): Promise<SparseHasManyResult> {
323+
const response: ResourceCollectionDocument = await this.currentUser.authenticatedAJAX({
324+
url: this.getHasManyLink(relationshipName),
325+
data: {
326+
fields: buildFieldsParam(fieldset),
327+
...options.queryParams,
328+
},
329+
...options.ajaxOptions,
330+
});
331+
332+
const { data, meta, links } = response;
333+
334+
return {
335+
sparseModels: data.map(parseSparseResource),
336+
meta,
337+
...(links ? { links } : {}),
338+
};
339+
}
340+
341+
/**
342+
* Fetch the entirety of a has-many relationship using sparse fieldsets.
343+
* See `sparseHasMany` above.
344+
*/
345+
async sparseLoadAll<T extends OsfModel>(
346+
this: T,
347+
relationshipName: RelationshipsFor<T>,
348+
fieldset: SparseFieldset,
349+
options: RequestOptions = {},
350+
): Promise<SparseModel[]> {
351+
const sparseModels: SparseModel[] = [];
352+
let page = 1;
353+
let totalPages = 0;
354+
355+
do { // eslint-disable-next-line no-await-in-loop
356+
const response = await this.sparseHasMany(relationshipName, fieldset, {
357+
...options,
358+
queryParams: {
359+
...options.queryParams,
360+
page,
361+
'page[size]': 100,
362+
},
363+
});
364+
365+
sparseModels.push(...response.sparseModels);
366+
totalPages = Math.ceil(response.meta.total / response.meta.per_page);
367+
page++;
368+
} while (page <= totalPages);
369+
370+
return sparseModels;
371+
}
274372
}

app/models/registration-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface AbstractQuestion {
88
required?: boolean;
99
description?: string;
1010
properties?: Subquestion[];
11+
options?: string[];
1112
}
1213

1314
export interface Subquestion extends AbstractQuestion {

0 commit comments

Comments
 (0)