Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [25.12.0] - 2025-06-13
### Added
- Google File Picker workflow
- Misc. improvements

## [25.11.0] - 2025-06-11
### Added
- Manual GUID and DOI assignment during Preprint and Registration Creation
Expand Down
6 changes: 6 additions & 0 deletions app/config/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ declare const config: {
doiUrlPrefix: string;
dataciteTrackerRepoId: string;
dataCiteTrackerUrl: string;
googleFilePicker: {
GOOGLE_FILE_PICKER_SCOPES: string;
GOOGLE_FILE_PICKER_CLIENT_ID: string;
GOOGLE_FILE_PICKER_API_KEY: string;
GOOGLE_FILE_PICKER_APP_ID: number;
}
};
social: {
twitter: {
Expand Down
1 change: 1 addition & 0 deletions app/guid-node/files/provider/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
>
<div local-class='FileBrowser'>
<FileBrowser
@configuredStorageAddon={{this.model.configuredStorageAddon}}
@manager={{manager}}
@selectable={{not this.model.providerTask.value.currentUser.viewOnlyToken}}
@enableUpload={{true}}
Expand Down
3 changes: 1 addition & 2 deletions app/models/authorized-account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Model, { attr } from '@ember-data/model';
import Model, { attr} from '@ember-data/model';
import { OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';

export enum ConnectedCapabilities {
Expand Down Expand Up @@ -36,7 +36,6 @@ export default class AuthorizedAccountModel extends Model {
return;
}


async getItemInfo(this: AuthorizedAccountModel, _itemId: string) : Promise<any> {
// To be implemented in child classes
return;
Expand Down
7 changes: 5 additions & 2 deletions app/models/authorized-storage-account.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { AsyncBelongsTo, belongsTo } from '@ember-data/model';
import { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
import { waitFor } from '@ember/test-waiters';
import { task } from 'ember-concurrency';
import { ConnectedStorageOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';

import ExternalStorageServiceModel from './external-storage-service';
import AuthorizedAccountModel from './authorized-account';
import UserReferenceModel from './user-reference';

export default class AuthorizedStorageAccountModel extends AuthorizedAccountModel {
@attr('fixstring') serializeOauthToken!: string;
@attr('fixstring') oauthToken!: string;

@belongsTo('user-reference', { inverse: 'authorizedStorageAccounts' })
readonly accountOwner!: AsyncBelongsTo<UserReferenceModel> & UserReferenceModel;

Expand Down
2 changes: 1 addition & 1 deletion app/models/index-card-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface SearchFilter {
filterType?: string;
}

export const ShareMoreThanTenThousand = 'https://share.osf.io/vocab/2023/trove/ten-thousands-and-more';
export const ShareMoreThanTenThousand = 'trove:ten-thousands-and-more';

export default class IndexCardSearchModel extends Model {
@attr('string') cardSearchText!: string;
Expand Down
2 changes: 1 addition & 1 deletion app/models/search-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ export default class SearchResultModel extends Model {
}

get isWithdrawn() {
return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['https://osf.io/vocab/2022/withdrawal'];
return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['osf:withdrawal'];
}

get configuredAddonNames() {
Expand Down
1 change: 1 addition & 0 deletions app/packages/addons-service/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export default class Provider {
accountOwner: this.userReference,
});
await newAccount.save();
newAccount.initiateOauth = null;
return newAccount;
}

Expand Down
13 changes: 13 additions & 0 deletions config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ const {
SHARE_SEARCH_URL: shareSearchUrl = 'http://localhost:8003/api/v2/search/creativeworks/_search',
SOURCEMAPS_ENABLED: sourcemapsEnabled = true,
SHOW_DEV_BANNER = false,

GOOGLE_FILE_PICKER_SCOPES,
/* eslint-disable-next-line max-len */
GOOGLE_FILE_PICKER_CLIENT_ID,
GOOGLE_FILE_PICKER_API_KEY,
GOOGLE_FILE_PICKER_APP_ID,

} = { ...process.env, ...localConfig };

module.exports = function(environment) {
Expand Down Expand Up @@ -224,6 +231,12 @@ module.exports = function(environment) {
doiUrlPrefix: 'https://doi.org/',
dataciteTrackerRepoId,
dataCiteTrackerUrl,
googleFilePicker: {
GOOGLE_FILE_PICKER_SCOPES,
GOOGLE_FILE_PICKER_CLIENT_ID,
GOOGLE_FILE_PICKER_API_KEY,
GOOGLE_FILE_PICKER_APP_ID,
},
},
social: {
twitter: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default class ActivityLogComponent extends Component {
public loadEmbeds = {
embed:
[
'group', 'linked_node', 'linked_registration', 'original_node',
'linked_node', 'linked_registration', 'original_node',
'template_node', 'user',
],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { action } from '@ember/object';
import { waitFor } from '@ember/test-waiters';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { TaskInstance } from 'ember-concurrency';
import { task, TaskInstance } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';


import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
Expand All @@ -12,6 +15,7 @@ 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 ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';


interface Args {
Expand All @@ -25,6 +29,8 @@ export default class ConfiguredAddonEdit extends Component<Args> {
@tracked selectedFolder = this.args.configuredAddon?.rootFolder;
@tracked selectedFolderDisplayName = this.args.configuredAddon?.rootFolderName;
@tracked currentItems: Item[] = [];
@tracked isWBGoogleDrive = false;
@tracked accountId!: string;

originalName = this.displayName;
originalRootFolder = this.selectedFolder;
Expand All @@ -34,6 +40,7 @@ export default class ConfiguredAddonEdit extends Component<Args> {
super(owner, args);
if (this.args.configuredAddon) {
if (this.args.configuredAddon instanceof ConfiguredStorageAddonModel) {
taskFor(this.loadExternalStorageService).perform();
this.defaultKwargs['itemType'] = ItemType.Folder;
}
if (this.args.configuredAddon instanceof ConfiguredCitationAddonModel) {
Expand All @@ -42,6 +49,7 @@ export default class ConfiguredAddonEdit extends Component<Args> {
}
if (this.args.authorizedAccount) {
if (this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
taskFor(this.loadExternalStorageService).perform();
this.defaultKwargs['itemType'] = ItemType.Folder;
}
if (this.args.authorizedAccount instanceof AuthorizedCitationAccountModel) {
Expand All @@ -50,6 +58,28 @@ export default class ConfiguredAddonEdit extends Component<Args> {
}
}

/**
* This is called only to authorize because the current implementation will throw an
* error because the "root folder" is not yet set.
*/
@task
@waitFor
async loadExternalStorageService() {
let external!: ExternalStorageServiceModel;
if (this.args.configuredAddon && this.args.configuredAddon instanceof ConfiguredStorageAddonModel) {
const baseAccount = await this.args.configuredAddon.baseAccount;
this.accountId = baseAccount?.id;
external = await this.args.configuredAddon.externalStorageService;
}
if (this.args.authorizedAccount && this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
external = await this.args.authorizedAccount.externalStorageService;

this.accountId = this.args.authorizedAccount.id;
}

this.isWBGoogleDrive = external?.wbKey === 'googledrive';
}

get requiresRootFolder() {
return !(
this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
Expand All @@ -58,6 +88,14 @@ export default class ConfiguredAddonEdit extends Component<Args> {
);
}

get isGoogleDrive(): boolean {
return this.isWBGoogleDrive;
}

get displayFileManager(): boolean {
return this.requiresRootFolder && !this.isGoogleDrive;
}

get invalidDisplayName() {
return !this.displayName || this.displayName?.trim().length === 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@
.item-name {
white-space: normal;
}

.picker-style {
white-space: pre-wrap;

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</span>
{{/if}}
</div>
{{#if this.requiresRootFolder }}
{{#if this.displayFileManager}}
<div local-class='input-wrapper'>
<b>
{{t 'addons.configure.selected-folder'}}
Expand Down Expand Up @@ -150,6 +150,13 @@
</tbody>
</table>
</AddonsService::FileManager>
{{else}}
<GoogleFilePickerWidget
@selectedFolderName={{this.selectedFolderDisplayName}}
@selectFolder={{action this.selectFolder}}
@isFolderPicker={{true}}
@accountId={{this.accountId}}
></GoogleFilePickerWidget>
{{/if}}
<div local-class='footer-buttons-wrapper'>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,8 @@ export default class AddonsServiceManagerComponent extends Component<Args> {
@waitFor
async createAuthorizedAccount(arg: AccountCreationArgs) {
if (this.selectedProvider) {
const newAccount = await taskFor(this.selectedProvider.createAuthorizedAccount)
return await taskFor(this.selectedProvider.createAuthorizedAccount)
.perform(arg);
return newAccount;
}
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,65 @@
// import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { waitFor } from '@ember/test-waiters';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import { task } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
import GoogleFilePickerWidget from 'osf-components/components/google-file-picker-widget/component';
import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component';

interface Args {
manager: StorageManager;
configuredStorageAddon: ConfiguredStorageAddonModel;
}

export default class FileBrowser extends Component<Args> {
@tracked createFolderModalOpen = false;
@tracked googlePickerComponent: GoogleFilePickerWidget | null = null;
@tracked dropzoneClickableElementId = '';
@tracked isWBGoogleDrive = false;
@tracked googleFilePickerRootFolder!: string;
@tracked accountId!: string;

constructor(owner: unknown, args: Args) {
super(owner, args);

taskFor(this.loadExternalStorageService).perform();
}

/**
* This is called only to authorize because the current implementation will throw an
* error because the "root folder" is not yet set.
*/
@task
@waitFor
async loadExternalStorageService() {
this.googleFilePickerRootFolder = this.args.configuredStorageAddon?.rootFolder;
const baseAccount = await this.args.configuredStorageAddon?.baseAccount;
this.accountId = baseAccount?.id;
const external = await this.args.configuredStorageAddon?.externalStorageService;
this.isWBGoogleDrive = external?.wbKey === 'googledrive';
}

get isGoogleDrive(): boolean {
return this.isWBGoogleDrive;
}

get isGoogleAuthorized(): boolean {
return this.googlePickerComponent?.isGFPDisabled || false;
}

@action
registerChild(child: GoogleFilePickerWidget) {
this.googlePickerComponent = child; // Store the child's instance
}

@action
openGoogleFilePicker(dropdown: any) {
dropdown.close();
if (this.googlePickerComponent) {
this.googlePickerComponent.createPicker();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,27 @@
>
{{t 'osf-components.file-browser.upload_file'}}
</Button>
{{#if this.isGoogleDrive}}
<Button
data-analytics-name='Add a file from Google Drive'
data-test-add-google-drive
@layout='fake-link'
{{on 'click' (fn this.openGoogleFilePicker dropdown)}}
disabled={{this.isGoogleAuthorized}}
>
{{t 'osf-components.file-browser.add-from-drive'}}
</Button>
{{/if}}
</dropdown.content>
</ResponsiveDropdown>
<FileBrowser::AddNew::CreateFolderModal @isOpen={{this.createFolderModalOpen}} @manager={{@manager}} />
{{#if this.isGoogleDrive}}
<GoogleFilePickerWidget
@isFolderPicker={{false}}
@accountId={{this.accountId}}
@onRegisterChild={{this.registerChild}}
@rootFolderId={{this.googleFilePickerRootFolder}}
@manager={{@manager}}
></GoogleFilePickerWidget>
{{/if}}
{{/let}}
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@
@isRegistration={{@manager.targetNode.isRegistration}}
/>
{{#if (and @manager.currentFolder.userCanUploadToHere @enableUpload)}}
<FileBrowser::AddNew @manager={{@manager}} @setClickableElementId={{uploadWidget.setClickableElementId}} />
<FileBrowser::AddNew
@manager={{@manager}}
@setClickableElementId={{uploadWidget.setClickableElementId}}
@configuredStorageAddon={{@configuredStorageAddon}}
/>
{{/if}}
{{/if}}
</div>
Expand Down
Loading