• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CenterForOpenScience / ember-osf-web / 4010544027

pending completion
4010544027

push

github

Yuhuai Liu
Merge branch 'release/23.01.0'

2462 of 3642 branches covered (67.6%)

Branch coverage included in aggregate %.

26 of 26 new or added lines in 6 files covered. (100.0%)

5575 of 7545 relevant lines covered (73.89%)

233.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.27
/app/models/node.ts
1
import { attr, belongsTo, hasMany, AsyncBelongsTo, AsyncHasMany } from '@ember-data/model';
2

3
import { computed } from '@ember/object';
4
import { alias, bool, equal, not } from '@ember/object/computed';
5
import { htmlSafe } from '@ember/string';
6
import { buildValidations, validator } from 'ember-cp-validations';
7

8
import getRelatedHref from 'ember-osf-web/utils/get-related-href';
9

10
import AbstractNodeModel from 'ember-osf-web/models/abstract-node';
11
import CitationModel from './citation';
12
import CommentModel from './comment';
13
import ContributorModel from './contributor';
14
import IdentifierModel from './identifier';
15
import InstitutionModel from './institution';
16
import LicenseModel from './license';
17
import LogModel from './log';
18
import NodeStorageModel from './node-storage';
19
import { Permission } from './osf-model';
20
import PreprintModel from './preprint';
21
import RegionModel from './region';
22
import RegistrationModel from './registration';
23
import SubjectModel from './subject';
24
import WikiModel from './wiki';
25

26
const Validations = buildValidations({
1✔
27
    title: [
28
        validator('presence', true),
29
    ],
30
});
31

32
const CollectableValidations = buildValidations({
1✔
33
    description: [
34
        validator('presence', {
35
            presence: true,
36
        }),
37
    ],
38
    license: [
39
        validator('presence', {
40
            presence: true,
41
        }),
42
    ],
43
    nodeLicense: [
44
        validator('presence', {
45
            presence: true,
46
        }),
47
        validator('node-license', {
48
            on: 'license',
49
        }),
50
    ],
51
    tags: [
52
        validator('presence', {
53
            presence: true,
54
            disabled: true,
55
        }),
56
    ],
57
}, {
58
    disabled: not('model.collectable'),
59
});
60

61
export enum NodeType {
62
    Fork = 'fork',
63
    Generic = 'generic',
64
    Registration = 'registration',
65
}
66

67
export enum NodeCategory {
68
    Data = 'data',
69
    Other = 'other',
70
    Project = 'project',
71
    Software = 'software',
72
    Analysis = 'analysis',
73
    Procedure = 'procedure',
74
    Hypothesis = 'hypothesis',
75
    Uncategorized = 'uncategorized',
76
    Communication = 'communication',
77
    Instrumentation = 'instrumentation',
78
    MethodsAndMeasures = 'methods and measures',
79
}
80

81
export interface NodeLicense {
82
    readonly copyrightHolders?: string;
83
    readonly year?: string;
84
}
85

86
export default class NodeModel extends AbstractNodeModel.extend(Validations, CollectableValidations) {
87
    @attr('fixstring') title!: string;
88
    @attr('fixstring') description!: string;
89
    @attr('node-category') category!: NodeCategory;
90
    @attr('boolean') currentUserIsContributor!: boolean;
91
    @attr('boolean') fork!: boolean;
92
    @alias('fork') isFork!: boolean;
93
    @attr('boolean') collection!: boolean;
94
    @attr('boolean') registration!: boolean;
95
    @attr('boolean') public!: boolean;
96
    @attr('date') dateCreated!: Date;
97
    @attr('date') dateModified!: Date;
98
    @attr('date') forkedDate!: Date;
99
    @attr('node-license') nodeLicense!: NodeLicense | null;
100
    @attr('fixstringarray') tags!: string[];
101
    @attr('fixstring') templateFrom!: string;
102
    @attr('string') analyticsKey?: string;
103
    @attr('boolean') preprint!: boolean;
104
    @attr('boolean') currentUserCanComment!: boolean;
105
    @attr('boolean') wikiEnabled!: boolean;
106

107
    @hasMany('contributor', { inverse: 'node' })
108
    contributors!: AsyncHasMany<ContributorModel> & ContributorModel[];
109

110
    @hasMany('contributor', { inverse: null })
111
    bibliographicContributors!: AsyncHasMany<ContributorModel>;
112

113
    @belongsTo('node', { inverse: 'children' })
114
    parent!: AsyncBelongsTo<NodeModel> & NodeModel;
115

116
    @belongsTo('region')
117
    region!: RegionModel;
118

119
    @hasMany('node', { inverse: 'parent' })
120
    children!: AsyncHasMany<NodeModel>;
121

122
    @hasMany('preprint', { inverse: 'node' })
123
    preprints!: AsyncHasMany<PreprintModel>;
124

125
    @hasMany('institution', { inverse: 'nodes' })
126
    affiliatedInstitutions!: AsyncHasMany<InstitutionModel> | InstitutionModel[];
127

128
    @hasMany('comment', { inverse: 'node' })
129
    comments!: AsyncHasMany<CommentModel>;
130

131
    @belongsTo('citation')
132
    citation!: AsyncBelongsTo<CitationModel> & CitationModel;
133

134
    @belongsTo('license', { inverse: null })
135
    license!: AsyncBelongsTo<LicenseModel> & LicenseModel;
136

137
    @hasMany('node', { inverse: null })
138
    linkedNodes!: AsyncHasMany<NodeModel> & NodeModel[];
139

140
    @hasMany('registration', { inverse: null })
141
    linkedRegistrations!: AsyncHasMany<RegistrationModel>;
142

143
    @hasMany('registration', { inverse: 'registeredFrom' })
144
    registrations!: AsyncHasMany<RegistrationModel>;
145

146
    @hasMany('node', { inverse: 'forkedFrom' })
147
    forks!: AsyncHasMany<NodeModel>;
148

149
    @belongsTo('node', { inverse: 'forks', polymorphic: true })
150
    forkedFrom!: (AsyncBelongsTo<NodeModel> & NodeModel) | (AsyncBelongsTo<RegistrationModel> & RegistrationModel);
151

152
    @belongsTo('node', { inverse: null })
153
    root!: AsyncBelongsTo<NodeModel> & NodeModel;
154

155
    @belongsTo('node-storage', { inverse: null })
156
    storage!: AsyncBelongsTo<NodeStorageModel> & NodeStorageModel;
157

158
    @hasMany('node', { inverse: null })
159
    linkedByNodes!: AsyncHasMany<NodeModel>;
160

161
    @hasMany('node', { inverse: null })
162
    linkedByRegistrations!: AsyncHasMany<RegistrationModel>;
163

164
    @hasMany('wiki', { inverse: 'node' })
165
    wikis!: AsyncHasMany<WikiModel>;
166

167
    @hasMany('log', { inverse: 'originalNode' })
168
    logs!: AsyncHasMany<LogModel>;
169

170
    @hasMany('identifier', { inverse: 'referent' })
171
    identifiers!: AsyncHasMany<IdentifierModel>;
172

173
    @hasMany('subject', { inverse: null, async: false })
174
    subjects!: SubjectModel[];
175

176
    // These are only computeds because maintaining separate flag values on
177
    // different classes would be a headache TODO: Improve.
178

179
    /**
180
     * Is this a project? Flag can be used to provide template-specific behavior
181
     * for different resource types.
182
     */
183
    @equal('constructor.modelName', 'node') isProject!: boolean;
184

185
    /**
186
     * Is this a registration? Flag can be used to provide template-specific
187
     * behavior for different resource types.
188
     */
189
    @equal('constructor.modelName', 'registration') isRegistration!: boolean;
190

191
    /**
192
     * Is this node being viewed through an anonymized, view-only link?
193
     */
194
    @bool('apiMeta.anonymous') isAnonymous!: boolean;
195

196
    /**
197
     * Does the current user have write permission on this node?
198
     */
199
    @computed('currentUserPermissions')
200
    get userHasWritePermission() {
201
        return Array.isArray(this.currentUserPermissions) && this.currentUserPermissions.includes(Permission.Write);
211✔
202
    }
203

204
    /**
205
     * Is the current user an admin on this node?
206
     */
207
    @computed('currentUserPermissions')
208
    get userHasAdminPermission() {
209
        return Array.isArray(this.currentUserPermissions) && this.currentUserPermissions.includes(Permission.Admin);
174✔
210
    }
211

212
    /**
213
     * Does the current user have read permission on this node?
214
     */
215
    @computed('currentUserPermissions')
216
    get userHasReadPermission() {
217
        return Array.isArray(this.currentUserPermissions) && this.currentUserPermissions.includes(Permission.Read);
153✔
218
    }
219

220
    @computed('currentUserPermissions.length')
221
    get currentUserIsReadOnly() {
222
        return Array.isArray(this.currentUserPermissions) && this.currentUserPermissions.includes(Permission.Read)
16✔
223
            && this.currentUserPermissions.length === 1;
224
    }
225

226
    /**
227
     * The type of this node.
228
     */
229
    @computed('isFork', 'isRegistration')
230
    get nodeType(): NodeType {
231
        if (this.isRegistration) {
63✔
232
            return NodeType.Registration;
35✔
233
        }
234
        if (this.isFork) {
28!
235
            return NodeType.Fork;
28✔
236
        }
237
        return NodeType.Generic;
×
238
    }
239

240
    // This is for the title helper, which does its own encoding of unsafe characters
241
    @computed('title')
242
    get unsafeTitle() {
243
        return htmlSafe(this.title);
33✔
244
    }
245

246
    @computed('id', 'root')
247
    get isRoot() {
248
        const rootId = (this as NodeModel).belongsTo('root').id();
110✔
249
        return !rootId || rootId === this.id;
110✔
250
    }
251

252
    // BaseFileItem override
253
    isNode = true;
555✔
254
    collectable = false;
555✔
255

256
    makeFork(): Promise<object> {
257
        const url = getRelatedHref(this.links.relationships!.forks);
3✔
258
        return this.currentUser.authenticatedAJAX({
3✔
259
            url,
260
            type: 'POST',
261
            headers: {
262
                'Content-Type': 'application/json',
263
            },
264
            data: JSON.stringify({
265
                data: { type: 'nodes' },
266
            }),
267
        });
268
    }
269

270
    /**
271
     * Sets the nodeLicense field defaults based on required fields from a License
272
     */
273
    setNodeLicenseDefaults(requiredFields: Array<keyof NodeLicense>): void {
274
        if (!requiredFields.length && this.nodeLicense) {
×
275
            // If the nodeLicense exists, notify property change so that validation is triggered
276
            this.notifyPropertyChange('nodeLicense');
×
277

278
            return;
×
279
        }
280

281
        const {
282
            copyrightHolders = '',
×
283
            year = new Date().getUTCFullYear().toString(),
×
284
        } = (this.nodeLicense || {});
×
285

286
        const nodeLicenseDefaults: NodeLicense = {
×
287
            copyrightHolders,
288
            year,
289
        };
290

291
        // Only set the required fields on nodeLicense
292
        const props = requiredFields.reduce(
×
293
            (acc, val) => ({ ...acc, [val]: nodeLicenseDefaults[val] }),
×
294
            {},
295
        );
296

297
        this.set('nodeLicense', props);
×
298
    }
299
}
300

301
declare module 'ember-data/types/registries/model' {
302
    export default interface ModelRegistry {
303
        node: NodeModel;
304
    } // eslint-disable-line semi
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc