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

CenterForOpenScience / ember-osf-web / 12549670634

30 Dec 2024 05:14PM UTC coverage: 63.021%. First build
12549670634

Pull #2437

github

web-flow
Merge bbe78892e into 8592e9dd7
Pull Request #2437: [ENG-6774] fix computing model / service names (and some citations, too!)

2782 of 4825 branches covered (57.66%)

Branch coverage included in aggregate %.

9 of 10 new or added lines in 3 files covered. (90.0%)

6886 of 10516 relevant lines covered (65.48%)

193.79 hits per line

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

80.14
/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
1
import EmberArray, { A } from '@ember/array';
2
import { action, notifyPropertyChange } from '@ember/object';
3
import { inject as service } from '@ember/service';
4
import { waitFor } from '@ember/test-waiters';
5
import Store from '@ember-data/store';
6
import Component from '@glimmer/component';
7
import { tracked } from '@glimmer/tracking';
8
import { task } from 'ember-concurrency';
9
import { taskFor } from 'ember-concurrency-ts';
10
import IntlService from 'ember-intl/services/intl';
11
import Toast from 'ember-toastr/services/toast';
12

13
import UserReferenceModel from 'ember-osf-web/models/user-reference';
14
import Provider, {AllProviderTypes, AllAuthorizedAccountTypes} from 'ember-osf-web/packages/addons-service/provider';
15
import CurrentUserService from 'ember-osf-web/services/current-user';
16
import AuthorizedAccountModel, { AccountCreationArgs } from 'ember-osf-web/models/authorized-account';
17
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
18
import AuthorizedCitationAccountModel from 'ember-osf-web/models/authorized-citation-account';
19
import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-computing-account';
20
import UserModel from 'ember-osf-web/models/user';
21

22
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
23
import ExternalComputingServiceModel from 'ember-osf-web/models/external-computing-service';
24
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
25
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
26
import getHref from 'ember-osf-web/utils/get-href';
27

28
import { FilterTypes } from '../manager/component';
29

30
enum UserSettingPageModes {
31
    TERMS = 'terms',
32
    ACCOUNT_CREATE = 'accountCreate',
33
    ACCOUNT_RECONNECT = 'accountReconnect',
34
}
35

36
interface Args {
37
    user: UserModel;
38
}
39

40
export default class UserAddonManagerComponent extends Component<Args> {
41
    @service store!: Store;
42
    @service currentUser!: CurrentUserService;
43
    @service intl!: IntlService;
44
    @service toast!: Toast;
45

46
    user = this.args.user;
7✔
47
    @tracked userReference!: UserReferenceModel;
48
    @tracked tabIndex = 0;
4✔
49

50
    possibleFilterTypes = Object.values(FilterTypes);
7✔
51
    @tracked filterTypeMapper = {
7✔
52
        [FilterTypes.STORAGE]: {
53
            modelName: 'external-storage-service',
54
            fetchProvidersTask: taskFor(this.getStorageAddonProviders),
55
            list: A([]) as EmberArray<Provider>,
56
            getAuthorizedAccountsTask: taskFor(this.getAuthorizedStorageAccounts),
57
            authorizedAccounts: [] as AuthorizedStorageAccountModel[],
58
            authorizedServiceIds: [] as string[],
59
        },
60
        [FilterTypes.CITATION_MANAGER]: {
61
            modelName: 'external-citation-service',
62
            fetchProvidersTask: taskFor(this.getCitationAddonProviders),
63
            list: A([]) as EmberArray<Provider>,
64
            getAuthorizedAccountsTask: taskFor(this.getAuthorizedCitationAccounts),
65
            authorizedAccounts: [] as AuthorizedCitationAccountModel[],
66
            authorizedServiceIds: [] as string[],
67
        },
68
        [FilterTypes.CLOUD_COMPUTING]: {
69
            modelName: 'external-computing-service',
70
            fetchProvidersTask: taskFor(this.getComputingAddonProviders),
71
            list: A([]) as EmberArray<Provider>,
72
            getAuthorizedAccountsTask: taskFor(this.getAuthorizedComputingAccounts),
73
            authorizedAccounts: [] as AuthorizedComputingAccountModel[],
74
            authorizedServiceIds: [] as string[],
75
        },
76
    };
77
    @tracked filterText = '';
7✔
78
    @tracked activeFilterType = FilterTypes.STORAGE;
7✔
79

80
    @tracked selectedProvider?: Provider;
81
    @tracked selectedAccount?: AllAuthorizedAccountTypes;
82

83
    @tracked pageMode?: UserSettingPageModes;
84

85
    @action
86
    changeTab(index: number) {
87
        this.tabIndex = index;
2✔
88
    }
89

90
    @action
91
    filterByAddonType(type: FilterTypes) {
92
        if (this.activeFilterType !== type) {
4!
93
            this.filterText = '';
4✔
94
        }
95
        this.activeFilterType = type;
4✔
96
        const activeFilterObject = this.filterTypeMapper[type];
4✔
97
        if (activeFilterObject.list.length === 0) {
4!
98
            activeFilterObject.fetchProvidersTask.perform();
4✔
99
            activeFilterObject.getAuthorizedAccountsTask.perform();
4✔
100
        }
101
    }
102

103
    get currentTypeAuthorizedAccounts() {
104
        const allAccounts = this.filterTypeMapper[this.activeFilterType].authorizedAccounts;
14✔
105
        const filteredAccounts = (allAccounts as AllAuthorizedAccountTypes[]).filter(
14✔
106
            (account: AllAuthorizedAccountTypes) => {
107
                const lowerCaseDisplayName = account.displayName.toLowerCase();
11✔
108
                return lowerCaseDisplayName.includes(this.filterText.toLowerCase());
11✔
109
            },
110
        );
111
        return filteredAccounts;
14✔
112
    }
113

114
    get filteredAddonProviders() {
115
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
21✔
116
        const possibleProviders = activeFilterObject.list;
21✔
117
        const textFilteredAddons = possibleProviders.filter(
21✔
118
            (provider: Provider) => provider.displayName.toLowerCase().includes(this.filterText.toLowerCase()),
51✔
119
        );
120
        return textFilteredAddons;
21✔
121
    }
122

123
    get currentTypeAuthorizedServiceIds() {
124
        return this.filterTypeMapper[this.activeFilterType].authorizedServiceIds;
2✔
125
    }
126

127
    get currentListIsLoading() {
128
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
35✔
129
        return activeFilterObject.fetchProvidersTask.isRunning;
35✔
130
    }
131

132
    @action
133
    connectNewProviderAccount(provider: Provider) {
134
        this.pageMode = UserSettingPageModes.TERMS;
2✔
135
        this.selectedProvider = provider;
2✔
136
    }
137

138
    @action
139
    acceptProviderTerms() {
140
        this.pageMode = UserSettingPageModes.ACCOUNT_CREATE;
2✔
141
    }
142

143
    @action
144
    navigateToReconnectProviderAccount(account: AllAuthorizedAccountTypes) {
145
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
2✔
146
        const possibleProviders = activeFilterObject.list;
2✔
147
        this.pageMode = UserSettingPageModes.ACCOUNT_RECONNECT;
2✔
148
        this.selectedAccount = account;
2✔
149
        let providerId = '';
2✔
150
        const accountType = (account.constructor as typeof AuthorizedAccountModel).modelName;
2✔
151
        switch (accountType) {
2!
152
        case 'authorized-storage-account':
153
            providerId = (account as AuthorizedStorageAccountModel).externalStorageService.get('id');
2✔
154
            break;
2✔
155
        case 'authorized-citation-account':
156
            providerId = (account as AuthorizedCitationAccountModel).externalCitationService.get('id');
×
157
            break;
×
158
        case 'authorized-computing-account':
NEW
159
            providerId = (account as AuthorizedComputingAccountModel).externalComputingService.get('id');
×
160
            break;
×
161
        default:
162
            break;
×
163
        }
164
        this.selectedProvider = possibleProviders.find(provider => provider.id === providerId);
5✔
165
    }
166

167
    @action
168
    cancelSetup() {
169
        this.pageMode = undefined;
4✔
170
        this.selectedProvider = undefined;
4✔
171
    }
172

173
    constructor(owner: unknown, args: Args) {
174
        super(owner, args);
7✔
175
        taskFor(this.initialize).perform();
7✔
176
    }
177

178

179
    @task
180
    @waitFor
181
    async initialize() {
182
        await taskFor(this.getUserReference).perform();
7✔
183
        await taskFor(this.getAuthorizedAccounts).perform();
7✔
184
        await taskFor(this.getAddonProviders).perform();
7✔
185
    }
186

187
    @task
188
    @waitFor
189
    async getUserReference() {
190
        const { user } = this;
7✔
191
        const _iri = user.links.iri;
7✔
192
        if (_iri) {
7!
193
            const userReferences = await this.store.query('user-reference', {
7✔
194
                filter: {user_uri: getHref(_iri)},
195
            });
196
            this.userReference = userReferences.firstObject;
7✔
197
        }
198
    }
199

200
    @task
201
    @waitFor
202
    async getAuthorizedStorageAccounts() {
203
        const { userReference } = this;
11✔
204
        const mappedObject = this.filterTypeMapper[FilterTypes.STORAGE];
11✔
205
        const accounts = (await userReference.authorizedStorageAccounts).toArray();
11✔
206
        mappedObject.authorizedAccounts = accounts;
11✔
207
        mappedObject.authorizedServiceIds = accounts.map(account => account.externalStorageService.get('id'));
11✔
208
        notifyPropertyChange(this, 'filterTypeMapper');
11✔
209
    }
210

211
    @task
212
    @waitFor
213
    async getAuthorizedCitationAccounts() {
214
        const { userReference } = this;
2✔
215
        const mappedObject = this.filterTypeMapper[FilterTypes.CITATION_MANAGER];
2✔
216
        const accounts = (await userReference.authorizedCitationAccounts).toArray();
2✔
217
        mappedObject.authorizedAccounts = accounts;
2✔
218
        mappedObject.authorizedServiceIds = accounts.map(account => account.externalCitationService.get('id'));
2✔
219
        notifyPropertyChange(this, 'filterTypeMapper');
2✔
220
    }
221

222
    @task
223
    @waitFor
224
    async getAuthorizedComputingAccounts() {
225
        const { userReference } = this;
2✔
226
        const mappedObject = this.filterTypeMapper[FilterTypes.CLOUD_COMPUTING];
2✔
227
        const accounts = (await userReference.authorizedComputingAccounts).toArray();
2✔
228
        mappedObject.authorizedAccounts = accounts;
2✔
229
        mappedObject.authorizedServiceIds = accounts.map(account => account.externalComputingService.get('id'));
2✔
230
        notifyPropertyChange(this, 'filterTypeMapper');
2✔
231
    }
232

233
    @task
234
    @waitFor
235
    async getAuthorizedAccounts() {
236
        const activeTypeMap = this.filterTypeMapper[this.activeFilterType];
9✔
237
        await taskFor(activeTypeMap.getAuthorizedAccountsTask).perform();
9✔
238
    }
239

240
    @task
241
    @waitFor
242
    async getStorageAddonProviders() {
243
        const activeFilterObject = this.filterTypeMapper[FilterTypes.STORAGE];
7✔
244
        const serviceStorageProviders = await taskFor(this.getExternalProviders)
7✔
245
            .perform(activeFilterObject.modelName) as ExternalStorageServiceModel[];
246
        const list = serviceStorageProviders.sort(this.providerSorter)
7✔
247
            .map(provider => new Provider(provider, this.currentUser));
32✔
248
        activeFilterObject.list = list;
7✔
249
    }
250

251
    @task
252
    @waitFor
253
    async getComputingAddonProviders() {
254
        const activeFilterObject = this.filterTypeMapper[FilterTypes.CLOUD_COMPUTING];
2✔
255
        const serviceComputingProviders = await taskFor(this.getExternalProviders)
2✔
256
            .perform(activeFilterObject.modelName) as ExternalComputingServiceModel[];
257
        activeFilterObject.list = serviceComputingProviders.sort(this.providerSorter)
2✔
258
            .map(provider => new Provider(provider, this.currentUser));
1✔
259
    }
260

261
    @task
262
    @waitFor
263
    async getCitationAddonProviders() {
264
        const activeFilterObject = this.filterTypeMapper[FilterTypes.CITATION_MANAGER];
2✔
265
        const serviceCitationProviders = await taskFor(this.getExternalProviders)
2✔
266
            .perform(activeFilterObject.modelName) as ExternalCitationServiceModel[];
267
        activeFilterObject.list = serviceCitationProviders.sort(this.providerSorter)
2✔
268
            .map(provider => new Provider(provider, this.currentUser));
3✔
269
    }
270

271
    @task
272
    @waitFor
273
    async getAddonProviders() {
274
        const activeTypeMap = this.filterTypeMapper[this.activeFilterType];
7✔
275
        await taskFor(activeTypeMap.fetchProvidersTask).perform();
7✔
276
    }
277

278
    providerSorter(a: AllProviderTypes, b: AllProviderTypes) {
279
        return a.displayName.localeCompare(b.displayName);
56✔
280
    }
281

282
    @task
283
    @waitFor
284
    async getExternalProviders(providerType: string) {
285
        const serviceProviderModels: AllProviderTypes[] = (await this.store.findAll(providerType)).toArray();
11✔
286
        return serviceProviderModels;
11✔
287
    }
288

289
    @task
290
    @waitFor
291
    async connectAccount(arg: AccountCreationArgs) {
292
        if (this.selectedProvider) {
1!
293
            await taskFor(this.selectedProvider!.createAuthorizedAccount)
1✔
294
                .perform(arg);
295
            this.cancelSetup();
1✔
296
            this.changeTab(1);
1✔
297
            await taskFor(this.getAuthorizedAccounts).perform();
1✔
298
        }
299
    }
300

301
    @task
302
    @waitFor
303
    async reconnectAccount(args: AccountCreationArgs) {
304
        if (this.selectedProvider && this.selectedAccount) {
1!
305
            await taskFor(this.selectedProvider.reconnectAuthorizedAccount)
1✔
306
                .perform(args, this.selectedAccount);
307
            this.cancelSetup();
1✔
308
            await taskFor(this.getAuthorizedAccounts).perform();
1✔
309
        }
310
    }
311

312
    @task
313
    @waitFor
314
    async createAuthorizedAccount(args: AccountCreationArgs) {
315
        if (this.selectedProvider) {
×
316
            return await taskFor(this.selectedProvider.createAuthorizedAccount)
×
317
                .perform(args);
318
        }
319
    }
320

321
    @task
322
    @waitFor
323
    async oauthFlowRefocus(newAccount: AllAuthorizedAccountTypes): Promise<boolean> {
324
        await newAccount.reload();
×
325
        if (newAccount.credentialsAvailable) {
×
326
            this.cancelSetup();
×
327
            this.changeTab(1);
×
328
            await taskFor(this.getAuthorizedAccounts).perform();
×
329
            return true;
×
330
        }
331
        return false;
×
332
    }
333

334
    @task
335
    @waitFor
336
    async disconnectAccount(account: AllAuthorizedAccountTypes) {
337
        try {
2✔
338
            const authorizedAccounts = this.filterTypeMapper[this.activeFilterType]
2✔
339
                .authorizedAccounts as AllAuthorizedAccountTypes[];
340
            authorizedAccounts.removeObject(account);
2✔
341
            await account.destroyRecord();
2✔
342
            await taskFor(this.filterTypeMapper[this.activeFilterType].getAuthorizedAccountsTask).perform();
2✔
343
            this.toast.success(this.intl.t('addons.accountCreate.disconnect-success'));
2✔
344
        } catch (e) {
345
            captureException(e);
×
346
            this.toast.error(getApiErrorMessage(e), this.intl.t('addons.accountCreate.disconnect-error'));
×
347
        }
348
    }
349
}
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