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

CenterForOpenScience / ember-osf-web / 12658699216

07 Jan 2025 08:20PM UTC coverage: 64.482%. First build
12658699216

Pull #2433

github

web-flow
Merge 4057df320 into 5bbb5a3c5
Pull Request #2433: [ENG-6669] Institutional Access project Request Modal

2738 of 4666 branches covered (58.68%)

Branch coverage included in aggregate %.

3 of 57 new or added lines in 5 files covered. (5.26%)

6973 of 10394 relevant lines covered (67.09%)

199.94 hits per line

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

49.02
/app/institutions/dashboard/-components/institutional-users-list/component.ts
1
import { task } from 'ember-concurrency';
2
import Component from '@glimmer/component';
3
import { tracked } from '@glimmer/tracking';
4
import { action } from '@ember/object';
5
import { inject as service } from '@ember/service';
6
import { waitFor } from '@ember/test-waiters';
7
import { restartableTask, timeout } from 'ember-concurrency';
8
import Intl from 'ember-intl/services/intl';
9

10
import InstitutionModel from 'ember-osf-web/models/institution';
11
import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department';
12
import Analytics from 'ember-osf-web/services/analytics';
13
import { RelationshipWithLinks } from 'osf-api';
14
import {MessageTypeChoices} from 'ember-osf-web/models/user-message';
15

16
interface Column {
17
    key: string;
18
    selected: boolean;
19
    label: string;
20
    sort_key: string | false;
21
    type: 'string' | 'date_by_month' | 'osf_link' | 'user_name' | 'orcid';
22
}
23

24
interface InstitutionalUsersListArgs {
25
    institution: InstitutionModel;
26
    departmentMetrics: InstitutionDepartmentsModel[];
27
}
28

29
export default class InstitutionalUsersList extends Component<InstitutionalUsersListArgs> {
30
    @service analytics!: Analytics;
31
    @service intl!: Intl;
32
    @service store;
33
    @service currentUser!: CurrentUser;
34

35
    institution?: InstitutionModel;
36

37
    departmentMetrics?: InstitutionDepartmentsModel[];
38

39
    // Properties
40
    @tracked department = this.defaultDepartment;
3✔
41
    @tracked sort = 'user_name';
3✔
42
    @tracked selectedDepartments: string[] = [];
×
43
    @tracked filteredUsers = [];
×
44
    @tracked messageModalShown = false;
3✔
45
    @tracked messageText = '';
×
NEW
46
    @tracked bccSender = false;
×
47
    @tracked replyTo = false;
×
48
    @tracked selectedUserId = null;
×
49
    @service toast!: Toast;
50

51
    @tracked columns: Column[] = [
3✔
52
        {
53
            key: 'user_name',
54
            sort_key: 'user_name',
55
            label: this.intl.t('institutions.dashboard.users_list.name'),
56
            selected: true,
57
            type: 'user_name',
58
        },
59
        {
60
            key: 'department',
61
            sort_key: 'department',
62
            label: this.intl.t('institutions.dashboard.users_list.department'),
63
            selected: true,
64
            type: 'string',
65
        },
66
        {
67
            key: 'osf_link',
68
            sort_key: false,
69
            label: this.intl.t('institutions.dashboard.users_list.osf_link'),
70
            selected: true,
71
            type: 'osf_link',
72
        },
73
        {
74
            key: 'orcid',
75
            sort_key: false,
76
            label: this.intl.t('institutions.dashboard.users_list.orcid'),
77
            selected: true,
78
            type: 'orcid',
79
        },
80
        {
81
            key: 'publicProjects',
82
            sort_key: 'public_projects',
83
            label: this.intl.t('institutions.dashboard.users_list.public_projects'),
84
            selected: true,
85
            type: 'string',
86
        },
87
        {
88
            key: 'privateProjects',
89
            sort_key: 'private_projects',
90
            label: this.intl.t('institutions.dashboard.users_list.private_projects'),
91
            selected: true,
92
            type: 'string',
93
        },
94
        {
95
            key: 'publicRegistrationCount',
96
            sort_key: 'public_registration_count',
97
            label: this.intl.t('institutions.dashboard.users_list.public_registration_count'),
98
            selected: true,
99
            type: 'string',
100
        },
101
        {
102
            key: 'embargoedRegistrationCount',
103
            sort_key: 'embargoed_registration_count',
104
            label: this.intl.t('institutions.dashboard.users_list.embargoed_registration_count'),
105
            selected: true,
106
            type: 'string',
107
        },
108
        {
109
            key: 'publishedPreprintCount',
110
            sort_key: 'published_preprint_count',
111
            label: this.intl.t('institutions.dashboard.users_list.published_preprint_count'),
112
            selected: true,
113
            type: 'string',
114
        },
115
        {
116
            key: 'publicFileCount',
117
            sort_key: 'public_file_count',
118
            label: this.intl.t('institutions.dashboard.users_list.public_file_count'),
119
            selected: false,
120
            type: 'string',
121
        },
122
        {
123
            key: 'userDataUsage',
124
            sort_key: 'storage_byte_count',
125
            label: this.intl.t('institutions.dashboard.users_list.storage_byte_count'),
126
            selected: false,
127
            type: 'string',
128
        },
129
        {
130
            key: 'accountCreationDate',
131
            sort_key: 'account_creation_date',
132
            label: this.intl.t('institutions.dashboard.users_list.account_created'),
133
            selected: false,
134
            type: 'date_by_month',
135
        },
136
        {
137
            key: 'monthLastLogin',
138
            sort_key: 'month_last_login',
139
            label: this.intl.t('institutions.dashboard.users_list.month_last_login'),
140
            selected: false,
141
            type: 'date_by_month',
142
        },
143
        {
144
            key: 'monthLastActive',
145
            sort_key: 'month_last_active',
146
            label: this.intl.t('institutions.dashboard.users_list.month_last_active'),
147
            selected: false,
148
            type: 'date_by_month',
149
        },
150
    ];
151

152
    @tracked selectedColumns: string[] = this.columns.filter(col => col.selected).map(col => col.key);
42✔
153
    // Private properties
154
    @tracked hasOrcid = false;
3✔
155
    @tracked totalUsers = 0;
3✔
156
    orcidUrlPrefix = 'https://orcid.org/';
3✔
157

158
    @action
159
    toggleColumnSelection(columnKey: string) {
160
        const column = this.columns.find(col => col.key === columnKey);
×
161
        if (column) {
×
162
            column.selected = !column.selected;
×
163
        }
164
    }
165

166
    get defaultDepartment() {
167
        return this.intl.t('institutions.dashboard.select_default');
15✔
168
    }
169

170
    get departments() {
171
        let departments = [this.defaultDepartment];
3✔
172

173
        if (this.args.institution && this.args.departmentMetrics) {
3✔
174
            const institutionDepartments = this.args.departmentMetrics.map((x: InstitutionDepartmentsModel) => x.name);
3✔
175
            departments = departments.concat(institutionDepartments);
1✔
176
        }
177
        return departments;
3✔
178
    }
179

180
    get isDefaultDepartment() {
181
        return this.department === this.defaultDepartment;
9✔
182
    }
183

184
    get queryUsers() {
185
        const query = {} as Record<string, string>;
9✔
186
        if (this.department && !this.isDefaultDepartment) {
9!
187
            query['filter[department]'] = this.department;
×
188
        }
189
        if (this.hasOrcid) {
9!
190
            query['filter[orcid_id][ne]'] = '';
×
191
        }
192
        if (this.sort) {
9!
193
            query.sort = this.sort;
9✔
194
        }
195
        return query;
9✔
196
    }
197

198
    downloadUrl(format: string) {
199
        const institutionRelationships = this.args.institution.links.relationships;
3✔
200
        const usersLink = (institutionRelationships!.user_metrics as RelationshipWithLinks).links.related.href;
3✔
201
        const userURL = new URL(usersLink!);
3✔
202
        userURL.searchParams.set('format', format);
3✔
203
        userURL.searchParams.set('page[size]', '10000');
3✔
204
        Object.entries(this.queryUsers).forEach(([key, value]) => {
3✔
205
            userURL.searchParams.set(key, value);
3✔
206
        });
207
        return userURL.toString();
3✔
208
    }
209

210
    get downloadCsvUrl() {
211
        return this.downloadUrl('csv');
1✔
212
    }
213

214
    get downloadTsvUrl() {
215
        return this.downloadUrl('tsv');
1✔
216
    }
217

218
    get downloadJsonUrl() {
219
        return this.downloadUrl('json_report');
1✔
220
    }
221

222
    @restartableTask
223
    @waitFor
224
    async searchDepartment(name: string) {
225
        await timeout(500);
×
226
        if (this.institution) {
×
227
            const depts: InstitutionDepartmentsModel[] = await this.args.institution.queryHasMany('departmentMetrics', {
×
228
                filter: {
229
                    name,
230
                },
231
            });
232
            return depts.map(dept => dept.name);
×
233
        }
234
        return [];
×
235
    }
236

237
    @action
238
    onSelectChange(department: string) {
239
        this.department = department;
×
240
    }
241

242
    @action
243
    sortInstitutionalUsers(sortBy: string) {
244
        if (this.sort === sortBy) {
3✔
245
            // If the current sort is ascending, toggle to descending
246
            this.sort = `-${sortBy}`;
1✔
247
        } else if (this.sort === `-${sortBy}`) {
2✔
248
            // If the current sort is descending, toggle to ascending
249
            this.sort = sortBy;
1✔
250
        } else {
251
            // Set to descending if it's a new sort field
252
            this.sort = `-${sortBy}`;
1✔
253
        }
254
    }
255

256
    @action
257
    cancelSelection() {
258
        this.selectedDepartments = [];
×
259
    }
260

261
    @action
262
    applyColumnSelection() {
263
        this.selectedColumns = this.columns.filter(col => col.selected).map(col => col.key);
×
264
    }
265

266
    @action
267
    toggleOrcidFilter(hasOrcid: boolean) {
268
        this.hasOrcid = hasOrcid;
×
269
    }
270

271
    @action
272
    clickToggleOrcidFilter(hasOrcid: boolean) {
273
        this.hasOrcid = !hasOrcid;
×
274
    }
275

276
    @action
277
    openMessageModal(userId: string) {
278
        this.selectedUserId = userId;
×
279
        this.messageModalShown = true;
×
280
    }
281

282
    @action
283
    toggleMessageModal(userId: string | null = null) {
×
284
        this.messageModalShown = !this.messageModalShown;
×
285
        this.selectedUserId = userId;
×
286
        if (!this.messageModalShown) {
×
287
            this.resetModalFields();
×
288
        }
289
    }
290

291
    @action
292
    resetModalFields() {
293
        this.messageText = '';
×
NEW
294
        this.bccSender = false;
×
295
        this.replyTo = false;
×
296
        this.selectedUserId = null;
×
297
    }
298

299
    @task
300
    @waitFor
301
    async sendMessage() {
302
        if (!this.messageText.trim()) {
×
303
            this.toast.error(this.intl.t('error.empty_message'));
×
304
            return;
×
305
        }
306

307
        try {
×
308
            const userMessage = this.store.createRecord('user-message', {
×
309
                messageText: this.messageText.trim(),
310
                messageType: MessageTypeChoices.InstitutionalRequest,
311
                bccSender: this.bccSender,
312
                replyTo: this.replyTo,
313
                institution: this.args.institution,
314
                user: this.selectedUserId,
315
            });
316

317
            await userMessage.save();
×
318
            this.toast.success(this.intl.t('institutions.dashboard.send_message_modal.message_sent_success'));
×
319
        } catch (error) {
320
            this.toast.error(this.intl.t('institutions.dashboard.send_message_modal.message_sent_failed'));
×
321
        } finally {
322
            this.messageModalShown = false;
×
323
        }
324
    }
325
}
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

© 2025 Coveralls, Inc