Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.

Commit 99a6b14

Browse files
authored
[ENG-7576] Linked service settings page (#2560)
* preliminary * another batch of updates * moar * fix tests * settings page * CR followup
1 parent a28878b commit 99a6b14

16 files changed

Lines changed: 251 additions & 42 deletions

File tree

app/guid-node/addons/index/controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { tracked } from 'tracked-built-ins';
66
enum FilterTypes {
77
STORAGE = 'additional-storage',
88
CITATION_MANAGER = 'citation-manager',
9+
VERIFIED_LINK = 'verified-link',
910
// CLOUD_COMPUTING = 'cloud-computing', // disabled because BOA is down
1011
}
1112
export default class GuidNodeAddonsController extends Controller {

app/guid-node/addons/index/template.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@
277277
<AddonsService::ConfiguredAddonEdit
278278
@configuredAddon={{manager.selectedConfiguration}}
279279
@authorizedAccount={{manager.selectedAccount}}
280+
@supportedResourceTypes={{manager.selectedProvider.provider.supportedResourceTypes}}
280281
@onSave={{perform manager.saveOrCreateConfiguration}}
281282
@onCancel={{manager.cancelSetup}}
282283
/>

app/models/configured-addon.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { task } from 'ember-concurrency';
55
import UserReferenceModel from 'ember-osf-web/models/user-reference';
66
import { tracked } from 'tracked-built-ins';
77
import { taskFor } from 'ember-concurrency-ts';
8+
import { SupportedResourceTypes } from 'ember-osf-web/models/external-link-service';
89
import { ConnectedStorageOperationNames, OperationKwargs } from './addon-operation-invocation';
910
import { ConnectedCapabilities } from './authorized-account';
1011

1112

1213
export interface ConfiguredAddonEditableAttrs {
1314
displayName: string;
1415
rootFolder: string;
16+
targetId: string;
17+
resourceType: SupportedResourceTypes;
1518
}
1619

1720
export default class ConfiguredAddonModel extends Model {

app/models/configured-link-addon.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
22
import { waitFor } from '@ember/test-waiters';
33
import { task } from 'ember-concurrency';
4-
import { ConnectedLinkOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
4+
import { ConnectedLinkOperationNames, Item, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
55
import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';
66

7-
import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
7+
import ExternalLinkServiceModel, { SupportedResourceTypes } from 'ember-osf-web/models/external-link-service';
88
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
9+
import { tracked } from 'tracked-built-ins';
10+
import { taskFor } from 'ember-concurrency-ts';
911
import ConfiguredAddonModel from './configured-addon';
1012

1113

1214
export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
13-
@attr('number') concurrentUploads!: number;
15+
@attr('string') targetId!: string;
16+
@attr('string') resourceType!: SupportedResourceTypes;
1417

1518
@belongsTo('external-link-service', { inverse: null })
1619
externalLinkService!: AsyncBelongsTo<ExternalLinkServiceModel> & ExternalLinkServiceModel;
1720

18-
@belongsTo('authorized-storage-account')
21+
@belongsTo('authorized-link-account')
1922
baseAccount!: AsyncBelongsTo<AuthorizedLinkAccountModel> & AuthorizedLinkAccountModel;
2023

2124
@belongsTo('resource-reference', { inverse: 'configuredLinkAddons' })
@@ -25,6 +28,10 @@ export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
2528
return (this as ConfiguredLinkAddonModel).belongsTo('externalLinkService').id();
2629
}
2730

31+
get hasRootFolder() {
32+
return false;
33+
}
34+
2835
@task
2936
@waitFor
3037
async getFolderItems(this: ConfiguredAddonModel, kwargs?: OperationKwargs) {
@@ -49,6 +56,15 @@ export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
4956
});
5057
return await newInvocation.save();
5158
}
59+
60+
@tracked targetItemName = '';
61+
62+
@task
63+
@waitFor
64+
async getTargetItemName(this: ConfiguredLinkAddonModel) {
65+
const response = await taskFor(this.getItemInfo).perform(this.targetId);
66+
this.targetItemName = (response.operationResult as Item).itemName;
67+
}
5268
}
5369

5470
declare module 'ember-data/types/registries/model' {

app/packages/addons-service/provider.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,25 @@ import ExternalComputingServiceModel from 'ember-osf-web/models/external-computi
2525
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
2626
import { notifyPropertyChange } from '@ember/object';
2727
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
28+
import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
29+
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
30+
import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
2831

2932
export type AllProviderTypes =
3033
ExternalStorageServiceModel |
3134
ExternalComputingServiceModel |
32-
ExternalCitationServiceModel;
35+
ExternalCitationServiceModel |
36+
ExternalLinkServiceModel;
3337
export type AllAuthorizedAccountTypes =
3438
AuthorizedStorageAccountModel |
3539
AuthorizedCitationAccountModel |
36-
AuthorizedComputingAccountModel;
40+
AuthorizedComputingAccountModel |
41+
AuthorizedLinkAccountModel;
3742
export type AllConfiguredAddonTypes =
3843
ConfiguredStorageAddonModel |
3944
ConfiguredCitationAddonModel |
40-
ConfiguredComputingAddonModel;
45+
ConfiguredComputingAddonModel |
46+
ConfiguredLinkAddonModel;
4147

4248
interface ProviderTypeMapper {
4349
getAuthorizedAccounts: Task<any, any>;
@@ -82,6 +88,11 @@ export default class Provider {
8288
createAuthorizedAccount: taskFor(this.createAuthorizedCitationAccount),
8389
createConfiguredAddon: taskFor(this.createConfiguredCitationAddon),
8490
},
91+
externalLinkService: {
92+
getAuthorizedAccounts: taskFor(this.getAuthorizedLinkAccounts),
93+
createAuthorizedAccount: taskFor(this.createAuthorizedLinkAccount),
94+
createConfiguredAddon: taskFor(this.createConfiguredLinkAddon),
95+
},
8596
};
8697

8798
@tracked configuredAddon?: AllConfiguredAddonTypes;
@@ -134,6 +145,8 @@ export default class Provider {
134145
this.providerMap = this.providerTypeMapper.externalComputingService;
135146
} else if (provider instanceof ExternalCitationServiceModel) {
136147
this.providerMap = this.providerTypeMapper.externalCitationService;
148+
} else if (provider instanceof ExternalLinkServiceModel) {
149+
this.providerMap = this.providerTypeMapper.externalLinkService;
137150
}
138151
taskFor(this.initialize).perform();
139152
}
@@ -211,6 +224,14 @@ export default class Provider {
211224
.filterBy('externalComputingService.id', this.provider.id).toArray();
212225
}
213226

227+
@task
228+
@waitFor
229+
async getAuthorizedLinkAccounts() {
230+
const authorizedLinkAccounts = await this.userReference.authorizedLinkAccounts;
231+
this.authorizedAccounts = authorizedLinkAccounts
232+
.filterBy('externalLinkService.id', this.provider.id).toArray();
233+
}
234+
214235
@task
215236
@waitFor
216237
async getAuthorizedAccounts() {
@@ -278,6 +299,25 @@ export default class Provider {
278299
return newAccount;
279300
}
280301

302+
@task
303+
@waitFor
304+
private async createAuthorizedLinkAccount(arg: AccountCreationArgs) {
305+
const { credentials, apiBaseUrl, displayName, initiateOauth } = arg;
306+
const newAccount = this.store.createRecord('authorized-link-account', {
307+
credentials,
308+
apiBaseUrl,
309+
initiateOauth,
310+
externalUserId: this.currentUser.user?.id,
311+
authorizedCapabilities: ['ACCESS', 'UPDATE'],
312+
scopes: [],
313+
externalLinkService: this.provider,
314+
accountOwner: this.userReference,
315+
displayName,
316+
});
317+
await newAccount.save();
318+
return newAccount;
319+
}
320+
281321
@task
282322
@waitFor
283323
public async createAuthorizedAccount(arg: AccountCreationArgs) {
@@ -339,6 +379,20 @@ export default class Provider {
339379
return await configuredComputingAddon.save();
340380
}
341381

382+
@task
383+
@waitFor
384+
private async createConfiguredLinkAddon(account: AuthorizedComputingAccountModel) {
385+
const configuredLinkAddon = this.store.createRecord('configured-link-addon', {
386+
externalLinkService: this.provider,
387+
accountOwner: this.userReference,
388+
authorizedResourceUri: this.node!.links.iri,
389+
baseAccount: account,
390+
connectedCapabilities: ['ACCESS', 'UPDATE'],
391+
});
392+
return await configuredLinkAddon.save();
393+
}
394+
395+
342396
@task
343397
@waitFor
344398
public async createConfiguredAddon(account: AllAuthorizedAccountTypes) {

lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation'
77
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
88
import AuthorizedCitationAccountModel from 'ember-osf-web/models/authorized-citation-account';
99
import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-computing-account';
10+
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
1011
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
1112
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
1213
import ConfiguredCitationAddonModel from 'ember-osf-web/models/configured-citation-addon';
1314
import ConfiguredComputingAddonModel from 'ember-osf-web/models/configured-computing-addon';
15+
import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
1416
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
1517

1618

@@ -25,9 +27,15 @@ export default class ConfiguredAddonEdit extends Component<Args> {
2527
@tracked selectedFolder = this.args.configuredAddon?.rootFolder;
2628
@tracked selectedFolderDisplayName = this.args.configuredAddon?.rootFolderName;
2729
@tracked currentItems: Item[] = [];
30+
@tracked selectedItem = (this.args.configuredAddon as ConfiguredLinkAddonModel).targetId;
31+
@tracked selectedItemDisplayName = (this.args.configuredAddon as ConfiguredLinkAddonModel).targetItemName;
32+
@tracked selectedResourceType = (this.args.configuredAddon as ConfiguredLinkAddonModel).resourceType;
2833

2934
originalName = this.displayName;
3035
originalRootFolder = this.selectedFolder;
36+
originalSelectedItem = this.selectedItem;
37+
originalResourceType = this.selectedResourceType;
38+
3139
defaultKwargs: any = {};
3240

3341
constructor(owner: unknown, args: Args) {
@@ -39,6 +47,9 @@ export default class ConfiguredAddonEdit extends Component<Args> {
3947
if (this.args.configuredAddon instanceof ConfiguredCitationAddonModel) {
4048
this.defaultKwargs['filterItems'] = ItemType.Collection;
4149
}
50+
if (this.args.configuredAddon instanceof ConfiguredLinkAddonModel) {
51+
// noop
52+
}
4253
}
4354
if (this.args.authorizedAccount) {
4455
if (this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
@@ -47,10 +58,17 @@ export default class ConfiguredAddonEdit extends Component<Args> {
4758
if (this.args.authorizedAccount instanceof AuthorizedCitationAccountModel) {
4859
this.defaultKwargs['filterItems'] = ItemType.Collection;
4960
}
61+
if (this.args.authorizedAccount instanceof AuthorizedLinkAccountModel) {
62+
// noop
63+
}
5064
}
5165
}
5266

53-
get requiresRootFolder() {
67+
get isLinkAddon() {
68+
return this.args.configuredAddon instanceof ConfiguredLinkAddonModel;
69+
}
70+
71+
get requiresFilesWidget() {
5472
return !(
5573
this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
5674
||
@@ -64,33 +82,40 @@ export default class ConfiguredAddonEdit extends Component<Args> {
6482

6583
get disableSave() {
6684
const displayNameUnchanged = this.displayName === this.originalName;
67-
const rootFolderUnchanged = this.requiresRootFolder && this.selectedFolder === this.originalRootFolder;
68-
const needsRootFolder = this.requiresRootFolder && !this.selectedFolder;
85+
const rootFolderUnchanged = this.requiresFilesWidget && !this.isLinkAddon
86+
&& this.selectedFolder === this.originalRootFolder;
87+
const targetIdUnchanged = this.isLinkAddon && this.originalSelectedItem === this.selectedItem;
88+
const resourceTypeUnchanged = this.originalResourceType === this.selectedResourceType;
89+
const needsResourceType = this.isLinkAddon && !this.selectedResourceType;
90+
const needsTargetId = this.isLinkAddon && !this.selectedItem;
91+
const needsRootFolder = this.requiresFilesWidget && !this.isLinkAddon && !this.selectedFolder;
6992

70-
if (this.invalidDisplayName || needsRootFolder) {
93+
if (this.invalidDisplayName || needsRootFolder || needsResourceType || needsTargetId) {
7194
return true;
7295
}
96+
if (this.isLinkAddon) {
97+
return targetIdUnchanged && resourceTypeUnchanged || this.args.onSave.isRunning;
98+
}
7399
return (rootFolderUnchanged && displayNameUnchanged) || this.args.onSave.isRunning;
74100
}
75101

76-
get nameValid() {
77-
return !this.invalidDisplayName && this.displayName !== this.originalName;
78-
}
79-
80-
get folderValid() {
81-
return !this.requiresRootFolder && this.selectedFolder && this.selectedFolder !== this.originalRootFolder;
82-
}
83-
84102
get onSaveArgs() {
85103
return {
86104
displayName: this.displayName,
87105
rootFolder: this.selectedFolder,
106+
targetId: this.selectedItem,
107+
resourceType: this.selectedResourceType,
88108
};
89109
}
90110

91111
@action
92-
selectFolder(folder: Item) {
93-
this.selectedFolder = folder.itemId;
94-
this.selectedFolderDisplayName = folder.itemName;
112+
selectItem(item: Item) {
113+
if (this.isLinkAddon) {
114+
this.selectedItem = item.itemId;
115+
this.selectedItemDisplayName = item.itemName;
116+
} else {
117+
this.selectedFolder = item.itemId;
118+
this.selectedFolderDisplayName = item.itemName;
119+
}
95120
}
96121
}

lib/osf-components/addon/components/addons-service/configured-addon-edit/styles.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
width: 100%;
2020
border: solid 1px $color-border-gray;
2121
border-collapse: collapse;
22+
margin-bottom: 1rem;
2223

2324
thead {
2425
border-bottom: solid 1px $color-border-gray;

0 commit comments

Comments
 (0)