Skip to content

Commit 32cfc9c

Browse files
authored
[ENG-6538] Require root folder to be selected when configuring addon (#2412)
- Ticket: [ENG-6538] - Feature flag: n/a # TODO - [x] Allow `getFolderItems` and `getItemInfo` to be done with the `thruAccount` arg (Right now it only uses the `thruAddon` arg which is for configured-addons) - [x] Have the `<AddonsService::FileManager>` be able to use either the `configuredAddon` or the `authorizedAccount` to get files - [x] Prevent configured-addon creation when a user confirms the authorized-account they want to use - [x] Save or create the configured-addon when user's click the "Save" button in the `<AddonService::ConfiguredAddonEdit>` component - [x] Update tests ## Purpose - Disallow users from saving if there is no root folder selected ## Summary of Changes - Disable "Save" button if no root folder is selected - Update default behavior to prevent a `configure-addon` from being created until after all the configuring has finished - Update tests
1 parent 53585c6 commit 32cfc9c

File tree

13 files changed

+93
-54
lines changed

13 files changed

+93
-54
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,10 @@
148148
<Button
149149
data-test-addon-confirm-setup-button
150150
data-analytics-name='Confirm Setup'
151-
disabled={{manager.confirmAccountSetup.isRunning}}
152151
@type='primary'
153-
{{on 'click' (perform manager.confirmAccountSetup manager.selectedAccount)}}
152+
{{on 'click' manager.confirmAccountSetup}}
154153
>
155-
{{if manager.confirmAccountSetup.isRunning (t 'addons.confirm.authorizing') (t 'general.confirm')}}
154+
{{t 'general.confirm'}}
156155
</Button>
157156
</div>
158157
{{else if (eq manager.pageMode 'configurationList')}}
@@ -269,7 +268,8 @@
269268
{{else if (eq manager.pageMode 'configure')}}
270269
<AddonsService::ConfiguredAddonEdit
271270
@configuredAddon={{manager.selectedConfiguration}}
272-
@onSave={{perform manager.saveConfiguration}}
271+
@authorizedAccount={{manager.selectedAccount}}
272+
@onSave={{perform manager.saveOrCreateConfiguration}}
273273
@onCancel={{manager.cancelSetup}}
274274
/>
275275
{{/if}}

app/models/addon-operation-invocation.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
22

33
import UserReferenceModel from 'ember-osf-web/models/user-reference';
4-
import ConfiguredAddonModel, { ConnectedOperationNames } from 'ember-osf-web/models/configured-addon';
4+
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
55
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
66

7+
export enum ConnectedOperationNames {
8+
HasRevisions = 'has_revisions',
9+
ListRootItems = 'list_root_items',
10+
ListChildItems = 'list_child_items',
11+
GetItemInfo = 'get_item_info',
12+
}
13+
14+
export interface OperationKwargs {
15+
itemId?: string;
16+
itemType?: ItemType;
17+
pageCursor?: string;
18+
}
19+
720
export enum InvocationStatus {
821
STARTING = 'STARTING',
922
GOING = 'GOING',
@@ -38,7 +51,7 @@ export interface Item {
3851
export default class AddonOperationInvocationModel extends Model {
3952
@attr('string') invocationStatus!: InvocationStatus;
4053
@attr('string') operationName!: ConnectedOperationNames;
41-
@attr('object', {snakifyForApi: true}) operationKwargs!: any;
54+
@attr('object', {snakifyForApi: true}) operationKwargs!: Partial<OperationKwargs>;
4255
@attr('object', {snakifyForApi: true}) operationResult!: OperationResult;
4356
@attr('date') created!: Date;
4457
@attr('date') modified!: Date;

app/models/authorized-account.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import Model, { attr } from '@ember-data/model';
2+
import { waitFor } from '@ember/test-waiters';
3+
import { task } from 'ember-concurrency';
4+
import { ConnectedOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
5+
6+
export enum ConnectedCapabilities {
7+
Access = 'ACCESS',
8+
Update = 'UPDATE',
9+
}
210

311
export interface AddonCredentialFields {
412
username?: string;
@@ -24,4 +32,29 @@ export default class AuthorizedAccountModel extends Model {
2432
@attr('boolean') initiateOauth!: boolean; // write-only
2533
@attr('fixstring') readonly authUrl!: string; // Only returned when POSTing to /authorized-xyz-accounts
2634
@attr('boolean') readonly credentialsAvailable!: boolean;
35+
36+
@task
37+
@waitFor
38+
async getFolderItems(this: AuthorizedAccountModel, kwargs?: OperationKwargs) {
39+
const operationKwargs = kwargs || {};
40+
const operationName = operationKwargs.itemId ? ConnectedOperationNames.ListChildItems :
41+
ConnectedOperationNames.ListRootItems;
42+
const newInvocation = this.store.createRecord('addon-operation-invocation', {
43+
operationName,
44+
operationKwargs,
45+
thruAccount: this,
46+
});
47+
return await newInvocation.save();
48+
}
49+
50+
@task
51+
@waitFor
52+
async getItemInfo(this: AuthorizedAccountModel, itemId: string) {
53+
const newInvocation = this.store.createRecord('addon-operation-invocation', {
54+
operationName: ConnectedOperationNames.GetItemInfo,
55+
operationKwargs: { itemId },
56+
thruAccount: this,
57+
});
58+
return await newInvocation.save();
59+
}
2760
}

app/models/configured-addon.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,11 @@ import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
22
import { waitFor } from '@ember/test-waiters';
33
import { task } from 'ember-concurrency';
44

5-
import { ItemType } from './addon-operation-invocation';
5+
import { ConnectedOperationNames, OperationKwargs } from './addon-operation-invocation';
66
import ResourceReferenceModel from './resource-reference';
77
import UserReferenceModel from './user-reference';
8+
import { ConnectedCapabilities } from './authorized-account';
89

9-
export enum ConnectedCapabilities {
10-
Access = 'ACCESS',
11-
Update = 'UPDATE',
12-
}
13-
14-
export enum ConnectedOperationNames {
15-
HasRevisions = 'has_revisions',
16-
ListRootItems = 'list_root_items',
17-
ListChildItems = 'list_child_items',
18-
GetItemInfo = 'get_item_info',
19-
}
20-
21-
export interface OperationKwargs {
22-
itemId?: string;
23-
itemType?: ItemType;
24-
pageCursor?: string;
25-
}
2610
export interface ConfiguredAddonEditableAttrs {
2711
displayName: string;
2812
rootFolder: string;

app/packages/files/service-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { task } from 'ember-concurrency';
66
import Intl from 'ember-intl/services/intl';
77
import Toast from 'ember-toastr/services/toast';
88
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
9-
import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/configured-addon';
9+
import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/addon-operation-invocation';
1010
import FileModel from 'ember-osf-web/models/file';
1111
import NodeModel from 'ember-osf-web/models/node';
1212
import { Permission } from 'ember-osf-web/models/osf-model';

app/packages/files/service-provider-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import CurrentUserService from 'ember-osf-web/services/current-user';
1212
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
1313
import { ErrorDocument } from 'osf-api';
1414
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
15-
import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/configured-addon';
15+
import { ConnectedOperationNames, ConnectedCapabilities } from 'ember-osf-web/models/addon-operation-invocation';
1616
import ServiceFile from 'ember-osf-web/packages/files/service-file';
1717
import { ExternalServiceCapabilities } from 'ember-osf-web/models/external-service';
1818

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import { tracked } from '@glimmer/tracking';
44
import { TaskInstance } from 'ember-concurrency';
55

66
import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
7+
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
78
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
89

910

1011
interface Args {
11-
configuredAddon: ConfiguredAddonModel;
12+
configuredAddon?: ConfiguredAddonModel;
13+
selectedAccount?: AuthorizedAccountModel;
1214
onSave: TaskInstance<void>;
1315
}
1416

1517
export default class ConfiguredAddonEdit extends Component<Args> {
16-
@tracked displayName = this.args.configuredAddon.displayName;
17-
@tracked selectedFolder = this.args.configuredAddon.rootFolder;
18+
@tracked displayName = this.args.configuredAddon?.displayName;
19+
@tracked selectedFolder = this.args.configuredAddon?.rootFolder;
1820
@tracked currentItems: Item[] = [];
1921

2022
defaultKwargs = {
@@ -26,7 +28,7 @@ export default class ConfiguredAddonEdit extends Component<Args> {
2628
}
2729

2830
get disableSave() {
29-
return this.invalidDisplayName || this.args.onSave.isRunning;
31+
return !this.selectedFolder || this.invalidDisplayName || this.args.onSave.isRunning;
3032
}
3133

3234
get onSaveArgs() {

lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
</div>
2323
<AddonsService::FileManager
2424
@configuredAddon={{@configuredAddon}}
25+
@authorizedAccount={{@authorizedAccount}}
2526
@defaultKwargs={{this.defaultKwargs}}
2627
@startingFolderId={{this.selectedFolder}}
2728
as |fileManager|

lib/osf-components/addon/components/addons-service/file-manager/component.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { assert } from '@ember/debug';
12
import { action } from '@ember/object';
23
import { inject as service } from '@ember/service';
34
import { waitFor } from '@ember/test-waiters';
@@ -8,13 +9,14 @@ import { taskFor } from 'ember-concurrency-ts';
89
import IntlService from 'ember-intl/services/intl';
910
import Toast from 'ember-toastr/services/toast';
1011

11-
import { OperationKwargs } from 'ember-osf-web/models/configured-addon';
12-
import { Item, ListItemsResult } from 'ember-osf-web/models/addon-operation-invocation';
12+
import { Item, ListItemsResult, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
13+
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
1314
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
1415
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
1516

1617
interface Args {
17-
configuredAddon: ConfiguredAddonModel;
18+
configuredAddon?: ConfiguredAddonModel;
19+
authorizedAccount?: AuthorizedAccountModel;
1820
defaultKwargs?: OperationKwargs;
1921
startingFolderId: string;
2022
}
@@ -23,6 +25,8 @@ export default class FileManager extends Component<Args> {
2325
@service intl!: IntlService;
2426
@service toast!: Toast;
2527

28+
@tracked operationInvocableModel!: ConfiguredAddonModel | AuthorizedAccountModel;
29+
2630
@tracked currentPath: Item[] = [];
2731
@tracked currentItems: Item[] = [];
2832
@tracked currentFolderId?: string;
@@ -33,17 +37,19 @@ export default class FileManager extends Component<Args> {
3337
private lastInvocation: any = null;
3438

3539
get isLoading() {
36-
return taskFor(this.args.configuredAddon.getFolderItems).isRunning ||
40+
return taskFor(this.operationInvocableModel.getFolderItems).isRunning ||
3741
taskFor(this.getStartingFolder).isRunning;
3842
}
3943

4044
get isError() {
41-
return taskFor(this.args.configuredAddon.getFolderItems).lastPerformed?.error ||
45+
return taskFor(this.operationInvocableModel.getFolderItems).lastPerformed?.error ||
4246
taskFor(this.getStartingFolder).lastPerformed?.error;
4347
}
4448

4549
constructor(owner: unknown, args: Args) {
4650
super(owner, args);
51+
assert('Must provide a configuredAddon or authorizedAccount', args.configuredAddon || args.authorizedAccount);
52+
this.operationInvocableModel = (args.configuredAddon || args.authorizedAccount)!;
4753
taskFor(this.initialize).perform();
4854
}
4955

@@ -85,10 +91,10 @@ export default class FileManager extends Component<Args> {
8591
@task
8692
@waitFor
8793
async getStartingFolder() {
88-
const { startingFolderId, configuredAddon } = this.args;
94+
const { startingFolderId } = this.args;
8995
try {
9096
if (startingFolderId) {
91-
const invocation = await taskFor(configuredAddon.getItemInfo).perform(startingFolderId);
97+
const invocation = await taskFor(this.operationInvocableModel.getItemInfo).perform(startingFolderId);
9298
const result = invocation.operationResult as Item;
9399
this.currentFolderId = result.itemId;
94100
this.currentPath = result.itemPath ? [...result.itemPath] : [];
@@ -109,7 +115,7 @@ export default class FileManager extends Component<Args> {
109115
kwargs.pageCursor = this.cursor;
110116
try {
111117
const getFolderArgs = !this.currentFolderId ? {} : kwargs;
112-
const invocation = await taskFor(this.args.configuredAddon.getFolderItems).perform(getFolderArgs);
118+
const invocation = await taskFor(this.operationInvocableModel.getFolderItems).perform(getFolderArgs);
113119
this.lastInvocation = invocation;
114120
const operationResult = invocation.operationResult as ListItemsResult;
115121
if (!this.currentFolderId) {

lib/osf-components/addon/components/addons-service/manager/component.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,8 @@ export default class AddonsServiceManagerComponent extends Component<Args> {
222222
return false;
223223
}
224224

225-
@task
226-
@waitFor
227-
async confirmAccountSetup(account: AllAuthorizedAccountTypes) {
228-
if (this.selectedProvider && this.selectedAccount) {
229-
this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon).perform(account);
230-
}
225+
@action
226+
confirmAccountSetup() {
231227
this.pageMode = PageMode.CONFIGURE;
232228
}
233229

@@ -242,8 +238,12 @@ export default class AddonsServiceManagerComponent extends Component<Args> {
242238

243239
@task
244240
@waitFor
245-
async saveConfiguration(args: ConfiguredAddonEditableAttrs) {
241+
async saveOrCreateConfiguration(args: ConfiguredAddonEditableAttrs) {
246242
try {
243+
if (!this.selectedConfiguration && this.selectedProvider && this.selectedAccount) {
244+
this.selectedConfiguration = await taskFor(this.selectedProvider.createConfiguredAddon)
245+
.perform(this.selectedAccount);
246+
}
247247
if (this.selectedConfiguration && this.selectedConfiguration instanceof ConfiguredStorageAddonModel) {
248248
this.selectedConfiguration.rootFolder = (args as ConfiguredAddonEditableAttrs).rootFolder;
249249
this.selectedConfiguration.displayName = args.displayName;

0 commit comments

Comments
 (0)