Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/guid-node/addons/index/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { tracked } from 'tracked-built-ins';
enum FilterTypes {
STORAGE = 'additional-storage',
CITATION_MANAGER = 'citation-manager',
VERIFIED_LINK = 'verified-link',
// CLOUD_COMPUTING = 'cloud-computing', // disabled because BOA is down
}
export default class GuidNodeAddonsController extends Controller {
Expand Down
1 change: 1 addition & 0 deletions app/guid-node/addons/index/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@
<AddonsService::ConfiguredAddonEdit
@configuredAddon={{manager.selectedConfiguration}}
@authorizedAccount={{manager.selectedAccount}}
@supportedResourceTypes={{manager.selectedProvider.provider.supportedResourceTypes}}
@onSave={{perform manager.saveOrCreateConfiguration}}
@onCancel={{manager.cancelSetup}}
/>
Expand Down
3 changes: 3 additions & 0 deletions app/models/configured-addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { task } from 'ember-concurrency';
import UserReferenceModel from 'ember-osf-web/models/user-reference';
import { tracked } from 'tracked-built-ins';
import { taskFor } from 'ember-concurrency-ts';
import { SupportedResourceTypes } from 'ember-osf-web/models/external-link-service';
import { ConnectedStorageOperationNames, OperationKwargs } from './addon-operation-invocation';
import { ConnectedCapabilities } from './authorized-account';


export interface ConfiguredAddonEditableAttrs {
displayName: string;
rootFolder: string;
targetId: string;
resourceType: SupportedResourceTypes;
}

export default class ConfiguredAddonModel extends Model {
Expand Down
24 changes: 20 additions & 4 deletions app/models/configured-link-addon.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
import { waitFor } from '@ember/test-waiters';
import { task } from 'ember-concurrency';
import { ConnectedLinkOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
import { ConnectedLinkOperationNames, Item, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';

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


export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
@attr('number') concurrentUploads!: number;
@attr('string') targetId!: string;
@attr('string') resourceType!: SupportedResourceTypes;

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

@belongsTo('authorized-storage-account')
@belongsTo('authorized-link-account')
baseAccount!: AsyncBelongsTo<AuthorizedLinkAccountModel> & AuthorizedLinkAccountModel;

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

get hasRootFolder() {
return false;
}

@task
@waitFor
async getFolderItems(this: ConfiguredAddonModel, kwargs?: OperationKwargs) {
Expand All @@ -49,6 +56,15 @@ export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
});
return await newInvocation.save();
}

@tracked targetItemName = '';

@task
@waitFor
async getTargetItemName(this: ConfiguredLinkAddonModel) {
const response = await taskFor(this.getItemInfo).perform(this.targetId);
this.targetItemName = (response.operationResult as Item).itemName;
}
}

declare module 'ember-data/types/registries/model' {
Expand Down
60 changes: 57 additions & 3 deletions app/packages/addons-service/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,25 @@ import ExternalComputingServiceModel from 'ember-osf-web/models/external-computi
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
import { notifyPropertyChange } from '@ember/object';
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';

export type AllProviderTypes =
ExternalStorageServiceModel |
ExternalComputingServiceModel |
ExternalCitationServiceModel;
ExternalCitationServiceModel |
ExternalLinkServiceModel;
export type AllAuthorizedAccountTypes =
AuthorizedStorageAccountModel |
AuthorizedCitationAccountModel |
AuthorizedComputingAccountModel;
AuthorizedComputingAccountModel |
AuthorizedLinkAccountModel;
export type AllConfiguredAddonTypes =
ConfiguredStorageAddonModel |
ConfiguredCitationAddonModel |
ConfiguredComputingAddonModel;
ConfiguredComputingAddonModel |
ConfiguredLinkAddonModel;

interface ProviderTypeMapper {
getAuthorizedAccounts: Task<any, any>;
Expand Down Expand Up @@ -82,6 +88,11 @@ export default class Provider {
createAuthorizedAccount: taskFor(this.createAuthorizedCitationAccount),
createConfiguredAddon: taskFor(this.createConfiguredCitationAddon),
},
externalLinkService: {
getAuthorizedAccounts: taskFor(this.getAuthorizedLinkAccounts),
createAuthorizedAccount: taskFor(this.createAuthorizedLinkAccount),
createConfiguredAddon: taskFor(this.createConfiguredLinkAddon),
},
};

@tracked configuredAddon?: AllConfiguredAddonTypes;
Expand Down Expand Up @@ -134,6 +145,8 @@ export default class Provider {
this.providerMap = this.providerTypeMapper.externalComputingService;
} else if (provider instanceof ExternalCitationServiceModel) {
this.providerMap = this.providerTypeMapper.externalCitationService;
} else if (provider instanceof ExternalLinkServiceModel) {
this.providerMap = this.providerTypeMapper.externalLinkService;
}
taskFor(this.initialize).perform();
}
Expand Down Expand Up @@ -211,6 +224,14 @@ export default class Provider {
.filterBy('externalComputingService.id', this.provider.id).toArray();
}

@task
@waitFor
async getAuthorizedLinkAccounts() {
const authorizedLinkAccounts = await this.userReference.authorizedLinkAccounts;
this.authorizedAccounts = authorizedLinkAccounts
.filterBy('externalLinkService.id', this.provider.id).toArray();
}

@task
@waitFor
async getAuthorizedAccounts() {
Expand Down Expand Up @@ -278,6 +299,25 @@ export default class Provider {
return newAccount;
}

@task
@waitFor
private async createAuthorizedLinkAccount(arg: AccountCreationArgs) {
const { credentials, apiBaseUrl, displayName, initiateOauth } = arg;
const newAccount = this.store.createRecord('authorized-link-account', {
credentials,
apiBaseUrl,
initiateOauth,
externalUserId: this.currentUser.user?.id,
authorizedCapabilities: ['ACCESS', 'UPDATE'],
scopes: [],
externalLinkService: this.provider,
accountOwner: this.userReference,
displayName,
});
await newAccount.save();
return newAccount;
}

@task
@waitFor
public async createAuthorizedAccount(arg: AccountCreationArgs) {
Expand Down Expand Up @@ -339,6 +379,20 @@ export default class Provider {
return await configuredComputingAddon.save();
}

@task
@waitFor
private async createConfiguredLinkAddon(account: AuthorizedComputingAccountModel) {
const configuredLinkAddon = this.store.createRecord('configured-link-addon', {
externalLinkService: this.provider,
accountOwner: this.userReference,
authorizedResourceUri: this.node!.links.iri,
baseAccount: account,
connectedCapabilities: ['ACCESS', 'UPDATE'],
});
return await configuredLinkAddon.save();
}


@task
@waitFor
public async createConfiguredAddon(account: AllAuthorizedAccountTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation'
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
import AuthorizedCitationAccountModel from 'ember-osf-web/models/authorized-citation-account';
import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-computing-account';
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
import ConfiguredCitationAddonModel from 'ember-osf-web/models/configured-citation-addon';
import ConfiguredComputingAddonModel from 'ember-osf-web/models/configured-computing-addon';
import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';


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

originalName = this.displayName;
originalRootFolder = this.selectedFolder;
originalSelectedItem = this.selectedItem;
originalResourceType = this.selectedResourceType;

defaultKwargs: any = {};

constructor(owner: unknown, args: Args) {
Expand All @@ -39,6 +47,9 @@ export default class ConfiguredAddonEdit extends Component<Args> {
if (this.args.configuredAddon instanceof ConfiguredCitationAddonModel) {
this.defaultKwargs['filterItems'] = ItemType.Collection;
}
if (this.args.configuredAddon instanceof ConfiguredLinkAddonModel) {
// noop
}
}
if (this.args.authorizedAccount) {
if (this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
Expand All @@ -47,10 +58,17 @@ export default class ConfiguredAddonEdit extends Component<Args> {
if (this.args.authorizedAccount instanceof AuthorizedCitationAccountModel) {
this.defaultKwargs['filterItems'] = ItemType.Collection;
}
if (this.args.authorizedAccount instanceof AuthorizedLinkAccountModel) {
// noop
}
}
}

get requiresRootFolder() {
get isLinkAddon() {
return this.args.configuredAddon instanceof ConfiguredLinkAddonModel;
}

get requiresFilesWidget() {
return !(
this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
||
Expand All @@ -64,33 +82,40 @@ export default class ConfiguredAddonEdit extends Component<Args> {

get disableSave() {
const displayNameUnchanged = this.displayName === this.originalName;
const rootFolderUnchanged = this.requiresRootFolder && this.selectedFolder === this.originalRootFolder;
const needsRootFolder = this.requiresRootFolder && !this.selectedFolder;
const rootFolderUnchanged = this.requiresFilesWidget && !this.isLinkAddon
&& this.selectedFolder === this.originalRootFolder;
const targetIdUnchanged = this.isLinkAddon && this.originalSelectedItem === this.selectedItem;
const resourceTypeUnchanged = this.originalResourceType === this.selectedResourceType;
const needsResourceType = this.isLinkAddon && !this.selectedResourceType;
const needsTargetId = this.isLinkAddon && !this.selectedItem;
const needsRootFolder = this.requiresFilesWidget && !this.isLinkAddon && !this.selectedFolder;

if (this.invalidDisplayName || needsRootFolder) {
if (this.invalidDisplayName || needsRootFolder || needsResourceType || needsTargetId) {
return true;
}
if (this.isLinkAddon) {
return targetIdUnchanged && resourceTypeUnchanged || this.args.onSave.isRunning;
}
return (rootFolderUnchanged && displayNameUnchanged) || this.args.onSave.isRunning;
}

get nameValid() {
return !this.invalidDisplayName && this.displayName !== this.originalName;
}

get folderValid() {
return !this.requiresRootFolder && this.selectedFolder && this.selectedFolder !== this.originalRootFolder;
}

get onSaveArgs() {
return {
displayName: this.displayName,
rootFolder: this.selectedFolder,
targetId: this.selectedItem,
resourceType: this.selectedResourceType,
};
}

@action
selectFolder(folder: Item) {
this.selectedFolder = folder.itemId;
this.selectedFolderDisplayName = folder.itemName;
selectItem(item: Item) {
if (this.isLinkAddon) {
this.selectedItem = item.itemId;
this.selectedItemDisplayName = item.itemName;
} else {
this.selectedFolder = item.itemId;
this.selectedFolderDisplayName = item.itemName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
width: 100%;
border: solid 1px $color-border-gray;
border-collapse: collapse;
margin-bottom: 1rem;

thead {
border-bottom: solid 1px $color-border-gray;
Expand Down
Loading