diff --git a/app/guid-node/addons/index/template.hbs b/app/guid-node/addons/index/template.hbs
index 7ff56a37dca..97ed3320b7a 100644
--- a/app/guid-node/addons/index/template.hbs
+++ b/app/guid-node/addons/index/template.hbs
@@ -148,11 +148,10 @@
{{else if (eq manager.pageMode 'configurationList')}}
@@ -269,7 +268,8 @@
{{else if (eq manager.pageMode 'configure')}}
{{/if}}
diff --git a/app/models/addon-operation-invocation.ts b/app/models/addon-operation-invocation.ts
index 2bf9205072f..7585e2ad621 100644
--- a/app/models/addon-operation-invocation.ts
+++ b/app/models/addon-operation-invocation.ts
@@ -1,9 +1,22 @@
import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
import UserReferenceModel from 'ember-osf-web/models/user-reference';
-import ConfiguredAddonModel, { ConnectedOperationNames } from 'ember-osf-web/models/configured-addon';
+import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
+export enum ConnectedOperationNames {
+ HasRevisions = 'has_revisions',
+ ListRootItems = 'list_root_items',
+ ListChildItems = 'list_child_items',
+ GetItemInfo = 'get_item_info',
+}
+
+export interface OperationKwargs {
+ itemId?: string;
+ itemType?: ItemType;
+ pageCursor?: string;
+}
+
export enum InvocationStatus {
STARTING = 'STARTING',
GOING = 'GOING',
@@ -38,7 +51,7 @@ export interface Item {
export default class AddonOperationInvocationModel extends Model {
@attr('string') invocationStatus!: InvocationStatus;
@attr('string') operationName!: ConnectedOperationNames;
- @attr('object', {snakifyForApi: true}) operationKwargs!: any;
+ @attr('object', {snakifyForApi: true}) operationKwargs!: Partial;
@attr('object', {snakifyForApi: true}) operationResult!: OperationResult;
@attr('date') created!: Date;
@attr('date') modified!: Date;
diff --git a/app/models/authorized-account.ts b/app/models/authorized-account.ts
index 02883c095ad..efc4a1725af 100644
--- a/app/models/authorized-account.ts
+++ b/app/models/authorized-account.ts
@@ -1,4 +1,12 @@
import Model, { attr } from '@ember-data/model';
+import { waitFor } from '@ember/test-waiters';
+import { task } from 'ember-concurrency';
+import { ConnectedOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
+
+export enum ConnectedCapabilities {
+ Access = 'ACCESS',
+ Update = 'UPDATE',
+}
export interface AddonCredentialFields {
username?: string;
@@ -24,4 +32,29 @@ export default class AuthorizedAccountModel extends Model {
@attr('boolean') initiateOauth!: boolean; // write-only
@attr('fixstring') readonly authUrl!: string; // Only returned when POSTing to /authorized-xyz-accounts
@attr('boolean') readonly credentialsAvailable!: boolean;
+
+ @task
+ @waitFor
+ async getFolderItems(this: AuthorizedAccountModel, kwargs?: OperationKwargs) {
+ const operationKwargs = kwargs || {};
+ const operationName = operationKwargs.itemId ? ConnectedOperationNames.ListChildItems :
+ ConnectedOperationNames.ListRootItems;
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName,
+ operationKwargs,
+ thruAccount: this,
+ });
+ return await newInvocation.save();
+ }
+
+ @task
+ @waitFor
+ async getItemInfo(this: AuthorizedAccountModel, itemId: string) {
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName: ConnectedOperationNames.GetItemInfo,
+ operationKwargs: { itemId },
+ thruAccount: this,
+ });
+ return await newInvocation.save();
+ }
}
diff --git a/app/models/configured-addon.ts b/app/models/configured-addon.ts
index 1bfdece8f20..3f094459d6a 100644
--- a/app/models/configured-addon.ts
+++ b/app/models/configured-addon.ts
@@ -2,27 +2,11 @@ import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
import { waitFor } from '@ember/test-waiters';
import { task } from 'ember-concurrency';
-import { ItemType } from './addon-operation-invocation';
+import { ConnectedOperationNames, OperationKwargs } from './addon-operation-invocation';
import ResourceReferenceModel from './resource-reference';
import UserReferenceModel from './user-reference';
+import { ConnectedCapabilities } from './authorized-account';
-export enum ConnectedCapabilities {
- Access = 'ACCESS',
- Update = 'UPDATE',
-}
-
-export enum ConnectedOperationNames {
- HasRevisions = 'has_revisions',
- ListRootItems = 'list_root_items',
- ListChildItems = 'list_child_items',
- GetItemInfo = 'get_item_info',
-}
-
-export interface OperationKwargs {
- itemId?: string;
- itemType?: ItemType;
- pageCursor?: string;
-}
export interface ConfiguredAddonEditableAttrs {
displayName: string;
rootFolder: string;
diff --git a/app/packages/files/service-file.ts b/app/packages/files/service-file.ts
index cab620ba686..2ce0e026509 100644
--- a/app/packages/files/service-file.ts
+++ b/app/packages/files/service-file.ts
@@ -6,7 +6,7 @@ import { task } from 'ember-concurrency';
import Intl from 'ember-intl/services/intl';
import Toast from 'ember-toastr/services/toast';
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
-import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/configured-addon';
+import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/addon-operation-invocation';
import FileModel from 'ember-osf-web/models/file';
import NodeModel from 'ember-osf-web/models/node';
import { Permission } from 'ember-osf-web/models/osf-model';
diff --git a/app/packages/files/service-provider-file.ts b/app/packages/files/service-provider-file.ts
index 3f234b0fb90..32c325e69aa 100644
--- a/app/packages/files/service-provider-file.ts
+++ b/app/packages/files/service-provider-file.ts
@@ -12,7 +12,7 @@ import CurrentUserService from 'ember-osf-web/services/current-user';
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
import { ErrorDocument } from 'osf-api';
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
-import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/configured-addon';
+import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/addon-operation-invocation';
import ServiceFile from 'ember-osf-web/packages/files/service-file';
import { ExternalServiceCapabilities } from 'ember-osf-web/models/external-service';
diff --git a/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts b/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
index 7dedf75c345..b1255dcaa5e 100644
--- a/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
+++ b/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
@@ -4,17 +4,19 @@ import { tracked } from '@glimmer/tracking';
import { TaskInstance } from 'ember-concurrency';
import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
+import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
interface Args {
- configuredAddon: ConfiguredAddonModel;
+ configuredAddon?: ConfiguredAddonModel;
+ selectedAccount?: AuthorizedAccountModel;
onSave: TaskInstance;
}
export default class ConfiguredAddonEdit extends Component {
- @tracked displayName = this.args.configuredAddon.displayName;
- @tracked selectedFolder = this.args.configuredAddon.rootFolder;
+ @tracked displayName = this.args.configuredAddon?.displayName;
+ @tracked selectedFolder = this.args.configuredAddon?.rootFolder;
@tracked currentItems: Item[] = [];
defaultKwargs = {
@@ -26,7 +28,7 @@ export default class ConfiguredAddonEdit extends Component {
}
get disableSave() {
- return this.invalidDisplayName || this.args.onSave.isRunning;
+ return !this.selectedFolder || this.invalidDisplayName || this.args.onSave.isRunning;
}
get onSaveArgs() {
diff --git a/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs b/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
index fcf5dcab974..812a67413af 100644
--- a/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
+++ b/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
@@ -22,6 +22,7 @@
{
@service intl!: IntlService;
@service toast!: Toast;
+ @tracked operationInvocableModel!: ConfiguredAddonModel | AuthorizedAccountModel;
+
@tracked currentPath: Item[] = [];
@tracked currentItems: Item[] = [];
@tracked currentFolderId?: string;
@@ -33,17 +37,19 @@ export default class FileManager extends Component {
private lastInvocation: any = null;
get isLoading() {
- return taskFor(this.args.configuredAddon.getFolderItems).isRunning ||
+ return taskFor(this.operationInvocableModel.getFolderItems).isRunning ||
taskFor(this.getStartingFolder).isRunning;
}
get isError() {
- return taskFor(this.args.configuredAddon.getFolderItems).lastPerformed?.error ||
+ return taskFor(this.operationInvocableModel.getFolderItems).lastPerformed?.error ||
taskFor(this.getStartingFolder).lastPerformed?.error;
}
constructor(owner: unknown, args: Args) {
super(owner, args);
+ assert('Must provide a configuredAddon or authorizedAccount', args.configuredAddon || args.authorizedAccount);
+ this.operationInvocableModel = (args.configuredAddon || args.authorizedAccount)!;
taskFor(this.initialize).perform();
}
@@ -85,10 +91,10 @@ export default class FileManager extends Component {
@task
@waitFor
async getStartingFolder() {
- const { startingFolderId, configuredAddon } = this.args;
+ const { startingFolderId } = this.args;
try {
if (startingFolderId) {
- const invocation = await taskFor(configuredAddon.getItemInfo).perform(startingFolderId);
+ const invocation = await taskFor(this.operationInvocableModel.getItemInfo).perform(startingFolderId);
const result = invocation.operationResult as Item;
this.currentFolderId = result.itemId;
this.currentPath = result.itemPath ? [...result.itemPath] : [];
@@ -109,7 +115,7 @@ export default class FileManager extends Component {
kwargs.pageCursor = this.cursor;
try {
const getFolderArgs = !this.currentFolderId ? {} : kwargs;
- const invocation = await taskFor(this.args.configuredAddon.getFolderItems).perform(getFolderArgs);
+ const invocation = await taskFor(this.operationInvocableModel.getFolderItems).perform(getFolderArgs);
this.lastInvocation = invocation;
const operationResult = invocation.operationResult as ListItemsResult;
if (!this.currentFolderId) {
diff --git a/lib/osf-components/addon/components/addons-service/manager/component.ts b/lib/osf-components/addon/components/addons-service/manager/component.ts
index 1b60bd7e735..46a5268837f 100644
--- a/lib/osf-components/addon/components/addons-service/manager/component.ts
+++ b/lib/osf-components/addon/components/addons-service/manager/component.ts
@@ -222,12 +222,8 @@ export default class AddonsServiceManagerComponent extends Component {
return false;
}
- @task
- @waitFor
- async confirmAccountSetup(account: AllAuthorizedAccountTypes) {
- if (this.selectedProvider && this.selectedAccount) {
- this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon).perform(account);
- }
+ @action
+ confirmAccountSetup() {
this.pageMode = PageMode.CONFIGURE;
}
@@ -242,8 +238,12 @@ export default class AddonsServiceManagerComponent extends Component {
@task
@waitFor
- async saveConfiguration(args: ConfiguredAddonEditableAttrs) {
+ async saveOrCreateConfiguration(args: ConfiguredAddonEditableAttrs) {
try {
+ if (!this.selectedConfiguration && this.selectedProvider && this.selectedAccount) {
+ this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon)
+ .perform(this.selectedAccount);
+ }
if (this.selectedConfiguration && this.selectedConfiguration instanceof ConfiguredStorageAddonModel) {
this.selectedConfiguration.rootFolder = (args as ConfiguredAddonEditableAttrs).rootFolder;
this.selectedConfiguration.displayName = args.displayName;
diff --git a/lib/osf-components/addon/components/addons-service/manager/template.hbs b/lib/osf-components/addon/components/addons-service/manager/template.hbs
index 6b6592a2639..15bdb6baafb 100644
--- a/lib/osf-components/addon/components/addons-service/manager/template.hbs
+++ b/lib/osf-components/addon/components/addons-service/manager/template.hbs
@@ -25,7 +25,7 @@
connectAccount=this.connectAccount
confirmAccountSetup=this.confirmAccountSetup
cancelSetup=this.cancelSetup
- saveConfiguration=this.saveConfiguration
+ saveOrCreateConfiguration=this.saveOrCreateConfiguration
pageMode=this.pageMode
headingText=this.headingText
removeConfiguredAddon=this.removeConfiguredAddon
diff --git a/mirage/views/addons.ts b/mirage/views/addons.ts
index ce57d70e72c..77abca34027 100644
--- a/mirage/views/addons.ts
+++ b/mirage/views/addons.ts
@@ -1,12 +1,11 @@
import { HandlerContext, ModelInstance, NormalizedRequestAttrs, Request, Response, Schema } from 'ember-cli-mirage';
import { timeout } from 'ember-concurrency';
-import { InvocationStatus, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
+import { ConnectedOperationNames, InvocationStatus, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
import AuthorizedCitationAccountModel from 'ember-osf-web/models/authorized-citation-account';
import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-computing-account';
import { AddonCredentialFields} from 'ember-osf-web/models/authorized-account';
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
-import { ConnectedOperationNames } from 'ember-osf-web/models/configured-addon';
import { CredentialsFormat } from 'ember-osf-web/models/external-service';
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
diff --git a/tests/integration/components/addons-service/configured-addon-edit/component-test.ts b/tests/integration/components/addons-service/configured-addon-edit/component-test.ts
index ddd3f0bcbb6..951842bf99e 100644
--- a/tests/integration/components/addons-service/configured-addon-edit/component-test.ts
+++ b/tests/integration/components/addons-service/configured-addon-edit/component-test.ts
@@ -38,7 +38,7 @@ module('Integration | Component | addons-service | configured-addon-edit', funct
const mirageConfiguredAddon = server.create('configured-storage-addon', {
displayName: 'My configured addon',
- rootFolder: 'rooty',
+ rootFolder: undefined,
externalStorageService: boxAddon,
accountOwner: userRef,
authorizedResource: resourceRef,
@@ -56,11 +56,13 @@ module('Integration | Component | addons-service | configured-addon-edit', funct
/>
`);
+ // Initial state when no folder is selected
assert.dom('[data-test-display-name-input]').hasValue('My configured addon', 'Display name is poopulated');
- assert.dom('[data-test-folder-path-option]').exists({ count: 1 }, 'Has root folder path option');
+ assert.dom('[data-test-go-to-root]').exists('Go to root button exists');
+ assert.dom('[data-test-folder-path-option]').doesNotExist('No folder option when at root');
assert.dom('[data-test-folder-link]').exists({ count: 5 }, 'Root folder has 5 folders');
assert.dom('[data-test-root-folder-option]').exists({ count: 5 }, 'Checkbox available for each folder option');
- assert.dom('[data-test-root-folder-save]').isEnabled('Save button is enabled');
+ assert.dom('[data-test-root-folder-save]').isDisabled('Save button is disabled when no folder is selected');
// updating and reseting the display name
assert.dom('[data-test-display-name-error]').doesNotExist('No error message initially');
@@ -68,13 +70,12 @@ module('Integration | Component | addons-service | configured-addon-edit', funct
assert.dom('[data-test-root-folder-save]').isDisabled('Save button is disabled');
assert.dom('[data-test-display-name-error]').exists('Error message is shown when display name is empty');
await fillIn('[data-test-display-name-input]', 'My configured addon');
- assert.dom('[data-test-root-folder-save]').isEnabled('Save button is enabled after display name is set');
assert.dom('[data-test-display-name-error]').doesNotExist('No error message after display name is set');
// Navigate into a folder
const folderLinks = this.element.querySelectorAll('[data-test-folder-link]');
await click(folderLinks[0]);
- assert.dom('[data-test-folder-path-option]').exists({ count: 2 }, 'Has root folder and subfolder path option');
+ assert.dom('[data-test-folder-path-option]').exists({ count: 1 }, 'Has root option and first folderoption');
// Navigate back to root
const navButtons = this.element.querySelectorAll('[data-test-folder-path-option]');
@@ -89,7 +90,7 @@ module('Integration | Component | addons-service | configured-addon-edit', funct
// Save
await click('[data-test-root-folder-save]');
const args = {
- rootFolder: 'rooty-1',
+ rootFolder: 'root-1-1',
displayName: 'My configured addon',
};