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

CenterForOpenScience / ember-osf-web / 12352381446

16 Dec 2024 12:12PM UTC coverage: 63.073%. First build
12352381446

Pull #2432

github

web-flow
Merge fba775c89 into 842d63d8c
Pull Request #2432: [ENG-6527] added response detail to configuration error toast

2781 of 4820 branches covered (57.7%)

Branch coverage included in aggregate %.

0 of 5 new or added lines in 1 file covered. (0.0%)

6885 of 10505 relevant lines covered (65.54%)

193.99 hits per line

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

69.15
/lib/osf-components/addon/components/addons-service/manager/component.ts
1
import EmberArray, { A } from '@ember/array';
2
import { action } 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, 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
import { TrackedObject } from 'tracked-built-ins';
13

14
import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';
15
import NodeModel from 'ember-osf-web/models/node';
16
import Provider, {
17
    AllAuthorizedAccountTypes, AllConfiguredAddonTypes,
18
} from 'ember-osf-web/packages/addons-service/provider';
19
import CurrentUserService from 'ember-osf-web/services/current-user';
20
import { ConfiguredAddonEditableAttrs } from 'ember-osf-web/models/configured-addon';
21
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
22
import { AccountCreationArgs} from 'ember-osf-web/models/authorized-account';
23
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
24
import ConfiguredCitationAddonModel from 'ember-osf-web/models/configured-citation-addon';
25

26
interface FilterSpecificObject {
27
    modelName: string;
28
    task: Task<any, any>;
29
    list: EmberArray<Provider>;
30
    configuredAddons?: EmberArray<AllConfiguredAddonTypes>;
31
}
32

33
enum PageMode {
34
    TERMS = 'terms',
35
    NEW_OR_EXISTING_ACCOUNT = 'newOrExistingAccount',
36
    ACCOUNT_SELECT = 'accountSelect',
37
    ACCOUNT_CREATE = 'accountCreate',
38
    CONFIRM = 'confirm',
39
    CONFIGURE = 'configure',
40
    CONFIGURATION_LIST = 'configurationList'
41
}
42

43
export enum FilterTypes {
44
    STORAGE = 'additional-storage',
45
    CITATION_MANAGER = 'citation-manager',
46
    CLOUD_COMPUTING = 'cloud-computing',
47
}
48

49
interface Args {
50
    node: NodeModel;
51
}
52

53
export default class AddonsServiceManagerComponent extends Component<Args> {
54
    @service store!: Store;
55
    @service currentUser!: CurrentUserService;
56
    @service intl!: IntlService;
57
    @service toast!: Toast;
58

59
    node = this.args.node;
4✔
60
    @tracked addonServiceNode?: ResourceReferenceModel;
61

62
    possibleFilterTypes = Object.values(FilterTypes);
4✔
63
    mapper: Record<FilterTypes, FilterSpecificObject> = {
4✔
64
        [FilterTypes.STORAGE]: {
65
            modelName: 'external-storage-service',
66
            task: taskFor(this.getStorageAddonProviders),
67
            list: A([]),
68
            configuredAddons: A([]),
69
        },
70
        [FilterTypes.CITATION_MANAGER]: {
71
            modelName: 'external-citation-service',
72
            task: taskFor(this.getCitationAddonProviders),
73
            list: A([]),
74
            configuredAddons: A([]),
75
        },
76
        [FilterTypes.CLOUD_COMPUTING]: {
77
            modelName: 'external-computing-service',
78
            task: taskFor(this.getCloudComputingProviders),
79
            list: A([]),
80
            configuredAddons: A([]),
81
        },
82
    };
83
    filterTypeMapper = new TrackedObject(this.mapper);
4✔
84
    @tracked filterText = '';
4✔
85
    @tracked activeFilterType: FilterTypes = FilterTypes.STORAGE;
4✔
86

87
    @tracked confirmRemoveConnectedLocation = false;
×
88
    @tracked pageMode?: PageMode;
89
    @tracked selectedProvider?: Provider;
90
    @tracked selectedConfiguration?: AllConfiguredAddonTypes;
91
    @tracked selectedAccount?: AllAuthorizedAccountTypes;
92

93
    @action
94
    filterByAddonType(type: FilterTypes) {
95
        if (this.activeFilterType !== type) {
5!
96
            this.filterText = '';
5✔
97
        }
98
        this.activeFilterType = type;
5✔
99
        const activeFilterObject = this.filterTypeMapper[type];
5✔
100
        if (activeFilterObject.list.length === 0) {
5✔
101
            activeFilterObject.task.perform();
4✔
102
        }
103
    }
104
    get filteredConfiguredProviders() {
105
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
5✔
106
        const possibleProviders = activeFilterObject.list;
5✔
107
        const textFilteredAddons = possibleProviders.filter(
5✔
108
            (provider: any) => provider.provider.displayName.toLowerCase().includes(this.filterText.toLowerCase()),
45✔
109
        );
110

111
        const configuredProviders = textFilteredAddons.filter((provider: Provider) => provider.isConfigured);
38✔
112

113
        return configuredProviders;
5✔
114
    }
115

116
    get filteredAddonProviders() {
117
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
15✔
118
        const possibleProviders = activeFilterObject.list;
15✔
119
        const textFilteredAddons = possibleProviders.filter(
15✔
120
            (provider: any) => provider.provider.displayName.toLowerCase().includes(this.filterText.toLowerCase()),
91✔
121
        );
122

123
        return textFilteredAddons;
15✔
124
    }
125

126
    get currentListIsLoading() {
127
        const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
21✔
128
        return activeFilterObject.task.isRunning || taskFor(this.initialize).isRunning;
21✔
129
    }
130

131
    @action
132
    async configureProvider(provider: Provider, configuredAddon: AllConfiguredAddonTypes) {
133
        this.cancelSetup();
1✔
134
        this.selectedProvider = provider;
1✔
135
        this.selectedConfiguration = configuredAddon;
1✔
136
        this.pageMode = PageMode.CONFIGURE;
1✔
137
    }
138

139
    @action
140
    listProviderConfigurations(provider: Provider) {
141
        this.cancelSetup();
2✔
142
        this.selectedProvider = provider;
2✔
143
        this.pageMode = PageMode.CONFIGURATION_LIST;
2✔
144
    }
145

146
    @action
147
    beginAccountSetup(provider: Provider) {
148
        this.cancelSetup();
1✔
149
        this.pageMode = PageMode.TERMS;
1✔
150
        this.selectedProvider = provider;
1✔
151
    }
152

153
    @action
154
    async acceptTerms() {
155
        await taskFor(this.selectedProvider!.getAuthorizedAccounts).perform();
1✔
156
        if(this.selectedProvider!.authorizedAccounts!.length > 0){
1!
157
            this.pageMode = PageMode.NEW_OR_EXISTING_ACCOUNT;
×
158
        } else {
159
            this.pageMode = PageMode.ACCOUNT_CREATE;
1✔
160
        }
161
    }
162

163
    @action
164
    chooseExistingAccount() {
165
        this.pageMode = PageMode.ACCOUNT_SELECT;
×
166
    }
167

168
    @action
169
    createNewAccount() {
170
        this.pageMode = PageMode.ACCOUNT_CREATE;
×
171
    }
172

173
    @action
174
    authorizeSelectedAccount() {
175
        if (this.selectedAccount && this.selectedAccount.credentialsAvailable) {
×
176
            this.pageMode = PageMode.CONFIRM;
×
177
        } else {
178
            this.pageMode = PageMode.ACCOUNT_CREATE;
×
179
        }
180
    }
181

182
    @task
183
    @waitFor
184
    async createAuthorizedAccount(arg: AccountCreationArgs) {
185
        if (this.selectedProvider) {
1!
186
            const newAccount = await taskFor(this.selectedProvider.createAuthorizedAccount)
1✔
187
                .perform(arg);
188
            return newAccount;
1✔
189
        }
190
        return undefined;
×
191
    }
192

193
    @task
194
    @waitFor
195
    async createConfiguredAddon(newAccount: AllAuthorizedAccountTypes) {
196
        if (this.selectedProvider) {
1!
197
            this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon).perform(newAccount);
1✔
198
        }
199
    }
200

201
    @task
202
    @waitFor
203
    async connectAccount(arg: AccountCreationArgs) {
204
        if (this.selectedProvider) {
1!
205
            const newAccount = await taskFor(this.createAuthorizedAccount).perform(arg);
1✔
206
            if (newAccount) {
1!
207
                await taskFor(this.createConfiguredAddon).perform(newAccount);
1✔
208
                this.pageMode = PageMode.CONFIGURE;
1✔
209
            }
210
        }
211
    }
212

213
    @task
214
    @waitFor
215
    async oauthFlowRefocus(newAccount: AllAuthorizedAccountTypes): Promise<boolean> {
216
        await newAccount.reload();
×
217
        if (newAccount.credentialsAvailable) {
×
218
            await taskFor(this.selectedProvider!.getAuthorizedAccounts).perform();
×
219
            this.selectedAccount = undefined;
×
220
            this.chooseExistingAccount();
×
221
            return true;
×
222
        }
223
        return false;
×
224
    }
225

226
    @action
227
    confirmAccountSetup() {
228
        this.pageMode = PageMode.CONFIGURE;
×
229
    }
230

231
    @action
232
    cancelSetup() {
233
        this.pageMode = undefined;
6✔
234
        this.selectedProvider = undefined;
6✔
235
        this.selectedConfiguration = undefined;
6✔
236
        this.selectedAccount = undefined;
6✔
237
        this.confirmRemoveConnectedLocation = false;
6✔
238
    }
239

240
    @task
241
    @waitFor
242
    async saveOrCreateConfiguration(args: ConfiguredAddonEditableAttrs) {
243
        try {
2✔
244
            if (!this.selectedConfiguration && this.selectedProvider && this.selectedAccount) {
2!
245
                this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon)
×
246
                    .perform(this.selectedAccount);
247
            }
248

249
            if (this.selectedConfiguration && (
2!
250
                this.selectedConfiguration instanceof ConfiguredStorageAddonModel ||
251
                this.selectedConfiguration instanceof ConfiguredCitationAddonModel)
252
            ) {
253
                this.selectedConfiguration.rootFolder = (args as ConfiguredAddonEditableAttrs).rootFolder;
2✔
254
                this.selectedConfiguration.displayName = args.displayName;
2✔
255
                await this.selectedConfiguration.save();
2✔
256
                this.toast.success(this.intl.t('addons.configure.success', {
2✔
257
                    configurationName: this.selectedConfiguration.displayName,
258
                }));
259
            }
260
            this.cancelSetup();
2✔
261
        } catch(e) {
NEW
262
            const baseMessage = this.intl.t('addons.configure.error', {
×
263
                configurationName: this.selectedConfiguration?.displayName,
264
            });
NEW
265
            if (e.errors && e.errors[0].detail) {
×
NEW
266
                const apiMessage = e.errors[0].detail;
×
NEW
267
                this.toast.error(`${baseMessage}: ${apiMessage}`);
×
268
            } else {
NEW
269
                this.toast.error(baseMessage);
×
270
            }
271

272
        }
273
    }
274

275
    @action
276
    selectAccount(account: AuthorizedStorageAccountModel) {
277
        this.selectedAccount = account;
×
278
    }
279

280
    constructor(owner: unknown, args: Args) {
281
        super(owner, args);
4✔
282
        taskFor(this.initialize).perform();
4✔
283
    }
284

285
    @task
286
    @waitFor
287
    async initialize() {
288
        await taskFor(this.getServiceNode).perform();
4✔
289
        await taskFor(this.getStorageAddonProviders).perform();
4✔
290
    }
291

292
    @task
293
    @waitFor
294
    async getServiceNode() {
295
        const references = await this.store.query('resource-reference', {
4✔
296
            filter: {resource_uri: this.node.links.iri},
297
        });
298
        if(references) {
4!
299
            this.addonServiceNode = references.firstObject;
4✔
300
        }
301
    }
302

303
    @task
304
    @waitFor
305
    async getStorageAddonProviders() {
306
        const activeFilterObject = this.filterTypeMapper[FilterTypes.STORAGE];
4✔
307

308
        if (this.addonServiceNode) {
4!
309
            const configuredAddons = await this.addonServiceNode.configuredStorageAddons;
4✔
310
            activeFilterObject.configuredAddons = A(configuredAddons.toArray());
4✔
311
        }
312

313
        const serviceStorageProviders: Provider[] =
314
            await taskFor(this.getExternalProviders)
4✔
315
                .perform(activeFilterObject.modelName, activeFilterObject.configuredAddons);
316
        activeFilterObject.list = A(serviceStorageProviders.sort(this.providerSorter));
4✔
317
    }
318

319
    @task
320
    @waitFor
321
    async getCloudComputingProviders() {
322
        const activeFilterObject = this.filterTypeMapper[FilterTypes.CLOUD_COMPUTING];
2✔
323

324
        if (this.addonServiceNode) {
2!
325
            const configuredAddons = await this.addonServiceNode.configuredComputingAddons;
2✔
326
            activeFilterObject.configuredAddons = A(configuredAddons.toArray());
2✔
327
        }
328

329
        const cloudComputingProviders: Provider[] =
330
            await taskFor(this.getExternalProviders)
2✔
331
                .perform(activeFilterObject.modelName, activeFilterObject.configuredAddons);
332
        activeFilterObject.list = cloudComputingProviders.sort(this.providerSorter);
2✔
333
    }
334

335
    @task
336
    @waitFor
337
    async getCitationAddonProviders() {
338
        const activeFilterObject = this.filterTypeMapper[FilterTypes.CITATION_MANAGER];
2✔
339

340
        if (this.addonServiceNode) {
2!
341
            const configuredAddons = await this.addonServiceNode.configuredCitationAddons;
2✔
342
            activeFilterObject.configuredAddons = A(configuredAddons.toArray());
2✔
343
        }
344

345
        const serviceCloudComputingProviders: Provider[] =
346
            await taskFor(this.getExternalProviders)
2✔
347
                .perform(activeFilterObject.modelName, activeFilterObject.configuredAddons);
348
        activeFilterObject.list = serviceCloudComputingProviders.sort(this.providerSorter);
2✔
349
    }
350

351
    providerSorter(a: Provider, b: Provider) {
352
        return a.provider.displayName.localeCompare(b.provider.displayName);
74✔
353
    }
354

355
    get projectEnabledAddons(): ConfiguredStorageAddonModel[] {
356
        return this.serviceProjectEnabledAddons();
5✔
357
    }
358

359
    get headingText() {
360
        const providerName = this.selectedProvider?.provider.displayName;
11✔
361
        let heading;
362
        switch (this.pageMode) {
11!
363
        case PageMode.TERMS:
364
            heading = this.intl.t('addons.terms.heading', { providerName });
1✔
365
            break;
1✔
366
        case PageMode.NEW_OR_EXISTING_ACCOUNT:
367
            heading = this.intl.t('addons.accountSelect.heading', { providerName });
×
368
            break;
×
369
        case PageMode.ACCOUNT_CREATE:
370
            heading = this.intl.t('addons.accountSelect.new-account');
1✔
371
            break;
1✔
372
        case PageMode.ACCOUNT_SELECT:
373
            heading = this.intl.t('addons.accountSelect.existing-account');
×
374
            break;
×
375
        case PageMode.CONFIRM:
376
            heading = this.intl.t('addons.confirm.heading', { providerName });
×
377
            break;
×
378
        case PageMode.CONFIGURE:
379
        case PageMode.CONFIGURATION_LIST:
380
            heading = this.intl.t('addons.configure.heading', { providerName });
4✔
381
            break;
4✔
382
        default:
383
            heading = this.intl.t('addons.heading');
5✔
384
            break;
5✔
385
        }
386
        return heading;
11✔
387
    }
388

389
    // Service API Methods
390

391
    @task
392
    @waitFor
393
    async getExternalProviders(providerType: string, configuredAddons?: EmberArray<AllConfiguredAddonTypes>) {
394
        const serviceProviderModels = (await this.store.findAll(providerType)).toArray();
8✔
395
        const serviceProviders = [] as Provider[];
8✔
396
        for (const provider of serviceProviderModels) {
8✔
397
            serviceProviders.addObject(new Provider(provider, this.currentUser, this.node, configuredAddons));
42✔
398
        }
399
        return serviceProviders;
8✔
400
    }
401

402
    serviceProjectEnabledAddons() {
403
        return this.addonServiceNode?.get('configuredStorageAddons').toArray() || [];
5!
404
    }
405
}
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