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

CenterForOpenScience / ember-osf-web / 13504987529

24 Feb 2025 05:51PM UTC coverage: 66.825% (-1.2%) from 68.015%
13504987529

push

github

adlius
Merge branch 'release/25.04.0'

3107 of 5068 branches covered (61.31%)

Branch coverage included in aggregate %.

452 of 809 new or added lines in 38 files covered. (55.87%)

9 existing lines in 3 files now uncovered.

7897 of 11399 relevant lines covered (69.28%)

189.72 hits per line

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

81.44
/lib/osf-components/addon/components/node-metadata-manager/component.ts
1
import Store from '@ember-data/store';
2
import { assert } from '@ember/debug';
3
import { action, notifyPropertyChange } from '@ember/object';
4
import { alias, or } from '@ember/object/computed';
5
import { inject as service } from '@ember/service';
6
import { waitFor } from '@ember/test-waiters';
7
import Component from '@glimmer/component';
8
import Intl from 'ember-intl/services/intl';
9
import Toast from 'ember-toastr/services/toast';
10
import { BufferedChangeset } from 'ember-changeset/types';
11
import { restartableTask, task } from 'ember-concurrency';
12
import { taskFor } from 'ember-concurrency-ts';
13

14
import NodeModel from 'ember-osf-web/models/node';
15
import CustomItemMetadataRecordModel from 'ember-osf-web/models/custom-item-metadata-record';
16
import { resourceTypeGeneralOptions } from 'ember-osf-web/models/custom-metadata';
17
import { Permission } from 'ember-osf-web/models/osf-model';
18
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
19
import { languageCodes, LanguageCode } from 'ember-osf-web/utils/languages';
20
import buildChangeset from 'ember-osf-web/utils/build-changeset';
21
import { tracked } from '@glimmer/tracking';
22

23
import { languageFromLanguageCode } from 'osf-components/components/file-metadata-manager/component';
24
import InstitutionModel from 'ember-osf-web/models/institution';
25
import LicenseModel from 'ember-osf-web/models/license';
26
import IdentifierModel from 'ember-osf-web/models/identifier';
27
import CurrentUser from 'ember-osf-web/services/current-user';
28
import UserModel from 'ember-osf-web/models/user';
29

30
interface Args {
31
    node: (NodeModel);
32
}
33

34
export interface NodeMetadataManager {
35
    editDescription: () => void;
36
    editFunding: () => void;
37
    editInstitutions: () => void;
38
    editResources: () => void;
39
    saveMetadata: () => void;
40
    saveNode: () => void;
41
    saveInstitutions: () => void;
42
    cancelMetadata: () => void;
43
    cancelNode: () => void;
44
    cancelInstitutions: () => void;
45
    toggleInstitution: () => void;
46
    metadata: CustomItemMetadataRecordModel;
47
    node: (NodeModel);
48
    changeset: BufferedChangeset;
49
    inEditMode: boolean;
50
    isSaving: boolean;
51
    userCanEdit: boolean;
52
    isDirty: boolean;
53
    isGatheringData: boolean;
54
    license: LicenseModel;
55
    identifiers: IdentifierModel[];
56
    isEditingInstitutions: boolean;
57
    isSavingInstitutions: boolean;
58
    affiliatedList: InstitutionModel[];
59
    user: UserModel;
60
}
61

62
export default class NodeMetadataManagerComponent extends Component<Args> {
63
    @service store!: Store;
64
    @service intl!: Intl;
65
    @service toast!: Toast;
66
    @service currentUser!: CurrentUser;
67

68
    @tracked metadata!: CustomItemMetadataRecordModel;
69
    node: (NodeModel) = this.args.node;
8✔
70
    @tracked changeset!: BufferedChangeset;
71
    @tracked nodeChangeset!: BufferedChangeset;
72
    @or(
73
        'isEditingDescription',
74
        'isEditingFunding',
75
        'isEditingResources',
76
        'isEditingInstitutions',
77
    ) inEditMode!: boolean;
78
    @tracked isEditingDescription = false;
8✔
79
    @tracked isEditingFunding = false;
7✔
80
    @tracked isEditingResources = false;
8✔
81
    @tracked isEditingInstitutions = false;
7✔
82
    @tracked userCanEdit!: boolean;
83
    @or(
84
        'getGuidMetadata.isRunning',
85
        'cancelMetadata.isRunning',
86
        'cancelNode.isRunning',
87
    ) isGatheringData!: boolean;
88
    @or('saveNode.isRunning', 'saveMetadata.isRunning', 'saveInstitutions.isRunning') isSaving!: boolean;
89
    @alias('changeset.isDirty') isDirty!: boolean;
90
    @alias('node.id') nodeId!: string;
91
    @tracked license!: LicenseModel;
92
    @tracked identifiers!: IdentifierModel[];
93
    @tracked guidType!: string | undefined;
94
    @tracked affiliatedList!: InstitutionModel[];
95
    @tracked currentAffiliatedList!: InstitutionModel[];
96
    resourceTypeGeneralOptions: string[] = resourceTypeGeneralOptions;
8✔
97
    languageCodes: LanguageCode[] = languageCodes;
8✔
98
    saveErrored = false;
8✔
99
    saveNodeErrored = false;
8✔
100

101
    constructor(owner: unknown, args: Args) {
102
        super(owner, args);
8✔
103
        assert(
8✔
104
            'You will need pass in a node or registration object to the NodeMetadataManager component to get metadata',
105
            Boolean(args.node),
106
        );
107
        try {
8✔
108
            taskFor(this.getGuidMetadata).perform();
8✔
109
            this.userCanEdit = this.node.currentUserPermissions.includes(Permission.Write);
8✔
110
        } catch (e) {
111
            const errorTitle = this.intl.t('osf-components.item-metadata-manager.error-getting-metadata');
×
112
            this.toast.error(getApiErrorMessage(e), errorTitle);
×
113
        }
114
    }
115

116
    @task
117
    @waitFor
118
    async getGuidMetadata() {
119
        if (this.node) {
8!
120
            const guidRecord = await this.store.findRecord('guid', this.node.id, {
8✔
121
                include: 'custom_metadata',
122
                resolve: false,
123
            });
124
            this.guidType = guidRecord.referentType;
8✔
125
            this.metadata = await guidRecord.customMetadata as CustomItemMetadataRecordModel;
8✔
126
            notifyPropertyChange(this, 'metadata');
8✔
127
            const node = this.node as NodeModel;
8✔
128
            this.affiliatedList = await node.queryHasMany(
8✔
129
                'affiliatedInstitutions', {
130
                    pageSize: 100,
131
                },
132
            );
133
            this.currentAffiliatedList = [...this.affiliatedList];
8✔
134
            this.license = await node.license;
8✔
135
            this.identifiers = await node.queryHasMany('identifiers');
8✔
136
            this.changeset = buildChangeset(this.metadata, null);
8✔
137
            this.changeset.languageObject = {
8✔
138
                code: this.metadata.language,
139
                name: this.languageFromCode,
140
            };
141
            this.changeset.execute();
8✔
142
            this.nodeChangeset = buildChangeset(this.node, null);
8✔
143
        }
144
    }
145

146
    get languageFromCode(){
147
        const languageCode = this.metadata?.language || '';
19!
148
        return languageFromLanguageCode(languageCode);
19✔
149
    }
150

151
    @action
152
    editDescription(){
153
        this.isEditingDescription = true;
3✔
154
    }
155

156
    @action
157
    editResources(){
158
        this.isEditingResources = true;
2✔
159
    }
160

161
    @action
162
    editFunding(){
163
        this.isEditingFunding = true;
1✔
164
    }
165

166
    @action
167
    editInstitutions(){
168
        this.isEditingInstitutions = true;
3✔
169
    }
170

171
    @action
172
    toggleInstitution(institution: InstitutionModel) {
173
        if (this.currentAffiliatedList.includes(institution)) {
2!
174
            this.currentAffiliatedList.removeObject(institution);
×
175
        } else {
176
            this.currentAffiliatedList.pushObject(institution);
2✔
177
        }
178
    }
179

180
    @task
181
    @waitFor
182
    async cancelMetadata(){
183
        if (this.saveErrored) {
2!
UNCOV
184
            await this.metadata.reload();
×
UNCOV
185
            this.metadata.rollbackAttributes();
×
UNCOV
186
            this.saveErrored = false;
×
187
        }
188
        this.changeset.rollback();
2✔
189
        this.changeset.languageObject = {
2✔
190
            code: this.metadata.language,
191
            name: this.languageFromCode,
192
        };
193
        this.changeset.execute();
2✔
194
        this.isEditingFunding = false;
2✔
195
        this.isEditingResources = false;
2✔
196
    }
197

198
    @task
199
    @waitFor
200
    async cancelNode(){
201
        if (this.saveNodeErrored){
2✔
202
            await this.node.reload();
1✔
203
            this.saveNodeErrored = false;
1✔
204
        }
205
        this.nodeChangeset.rollback();
2✔
206
        this.isEditingDescription = false;
2✔
207
    }
208

209
    @action
210
    cancelInstitutions(){
211
        this.currentAffiliatedList = [...this.affiliatedList];
2✔
212
        this.isEditingInstitutions = false;
2✔
213
    }
214

215
    @action
216
    changeLanguage(selected: LanguageCode) {
217
        const language = selected ? selected.code : '';
2!
218
        this.changeset.set('language', language);
2✔
219
    }
220

221
    @restartableTask
222
    @waitFor
223
    async saveMetadata(){
224
        try {
1✔
225
            await this.changeset.save();
1✔
226
            this.isEditingFunding = false;
1✔
227
            this.isEditingResources = false;
1✔
228
            this.saveErrored = false;
1✔
229
        } catch (e) {
UNCOV
230
            this.saveErrored = true;
×
UNCOV
231
            if (e.errors[0].source.pointer === '/data/attributes/funders/award_uri') {
×
232
                this.toast.error(this.intl.t('osf-components.funding-metadata.api_uri_validation_error'));
×
233
            } else {
UNCOV
234
                const errorTitle = this.intl.t('osf-components.item-metadata-manager.error-saving-metadata');
×
UNCOV
235
                this.toast.error(getApiErrorMessage(e), errorTitle);
×
236
            }
237
        }
238
    }
239

240
    @restartableTask
241
    @waitFor
242
    async saveNode(){
243
        try {
2✔
244
            await this.nodeChangeset.save();
2✔
245
            this.isEditingDescription = false;
1✔
246
            this.saveNodeErrored = false;
1✔
247
        } catch (e) {
248
            this.saveNodeErrored = true;
1✔
249
            const errorTitle = this.intl.t('osf-components.item-metadata-manager.error-saving-metadata');
1✔
250
            this.toast.error(getApiErrorMessage(e), errorTitle);
1✔
251
        }
252
    }
253

254
    @restartableTask
255
    @waitFor
256
    async saveInstitutions() {
257
        try {
2✔
258
            await this.node.updateM2MRelationship('affiliatedInstitutions', this.currentAffiliatedList);
2✔
259
            await this.node.reload();
1✔
260
            this.affiliatedList = [...this.currentAffiliatedList];
1✔
261
            this.isEditingInstitutions = false;
1✔
262
        } catch (e) {
263
            const errorMessage = this.intl.t('registries.drafts.draft.metadata.save_institutions_error');
1✔
264
            captureException(e, { errorMessage });
1✔
265
            this.toast.error(getApiErrorMessage(e), errorMessage);
1✔
266
            throw e;
1✔
267
        }
268
    }
269
}
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