• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CenterForOpenScience / ember-osf-web / 15638683025

13 Jun 2025 03:51PM UTC coverage: 67.382% (-0.3%) from 67.658%
15638683025

Pull #2578

github

web-flow
Merge 191144f38 into e2b59bd39
Pull Request #2578: Feature/pbs 25 10

3245 of 5298 branches covered (61.25%)

Branch coverage included in aggregate %.

25 of 82 new or added lines in 8 files covered. (30.49%)

3 existing lines in 3 files now uncovered.

8503 of 12137 relevant lines covered (70.06%)

185.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.45
/lib/osf-components/addon/components/google-file-picker-widget/component.ts
1
import Store from '@ember-data/store';
2
import { action } from '@ember/object';
3
import { waitFor } from '@ember/test-waiters';
4
import Component from '@glimmer/component';
5
import { tracked } from '@glimmer/tracking';
6
import { task } from 'ember-concurrency';
7
import { taskFor } from 'ember-concurrency-ts';
8
import config from 'ember-osf-web/config/environment';
9
import { Item } from 'ember-osf-web/models/addon-operation-invocation';
10
import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component';
11
import { inject as service } from '@ember/service';
12
import Intl from 'ember-intl/services/intl';
13

14
const {
15
    GOOGLE_FILE_PICKER_SCOPES,
16
    GOOGLE_FILE_PICKER_API_KEY,
17
    GOOGLE_FILE_PICKER_APP_ID,
18
} = config.OSF.googleFilePicker;
1✔
19

20
//
21
// 📚 Interface for Expected Arguments
22
//
23
interface Args {
24
  /**
25
   * selectFolder
26
   *
27
   * @description
28
   * A callback function passed into the component
29
   * that accepts a partial Item object and handles it (e.g., selects a file).
30
   */
31
  selectFolder?: (a: Partial<Item>) => void;
32
  onRegisterChild?: (a: GoogleFilePickerWidget) => void;
33
  selectedFolderName?: string;
34
  isFolderPicker: boolean;
35
  rootFolderId: string;
36
  manager: StorageManager;
37
  accountId: string;
38
}
39

40
//
41
// 📚 Extend Global Window Type
42
//
43
// Declares that `window` can optionally have a GoogleFilePickerWidget instance.
44
// This allows safe typing when accessing it elsewhere.
45
//
46
declare global {
47
  interface Window {
48
    GoogleFilePickerWidget?: GoogleFilePickerWidget;
49
    gapi?: any;
50
    google?: any;
51
  }
52
}
53

54
//
55
// GoogleFilePickerWidget Component
56
//
57
// @description
58
// An Ember Glimmer component that exposes itself to the global `window`
59
// so that external JavaScript (like Google Picker API callbacks)
60
// can interact with it directly.
61
//
62
export default class GoogleFilePickerWidget extends Component<Args> {
63
    @service intl!: Intl;
64
    @service store!: Store;
65
    @tracked folderName!: string | undefined;
NEW
66
    @tracked isFolderPicker = false;
×
NEW
67
    @tracked openGoogleFilePicker = false;
×
NEW
68
    @tracked visible = false;
×
NEW
69
    @tracked isGFPDisabled = true;
×
NEW
70
    pickerInited = false;
×
NEW
71
    selectFolder: any = undefined;
×
72
    accessToken!: string;
NEW
73
    scopes = GOOGLE_FILE_PICKER_SCOPES;
×
NEW
74
    apiKey = GOOGLE_FILE_PICKER_API_KEY;
×
NEW
75
    appId = GOOGLE_FILE_PICKER_APP_ID;
×
NEW
76
    mimeTypes = '';
×
NEW
77
    parentId = '';
×
78
    isMultipleSelect: boolean;
79
    title!: string;
80

81
    /**
82
     * Constructor
83
     *
84
     * @description
85
     * Initializes the GoogleFilePickerWidget component and exposes its key methods to the global `window` object
86
     * for integration with external JavaScript (e.g., Google Picker API).
87
     *
88
     * - Sets `window.GoogleFilePickerWidget` to the current component instance (`this`),
89
     *   allowing external scripts to call methods like `filePickerCallback()`.
90
     * - Captures the closure action `selectFolder` from `this.args` and assigns it directly to `window.selectFolder`,
91
     *   preserving the correct closure reference even outside of Ember's internal context.
92
     *
93
     * @param owner - The owner/context passed by Ember at component instantiation.
94
     * @param args - The arguments passed to the component, including closure actions like `selectFolder`.
95
     */
96
    constructor(owner: unknown, args: Args) {
NEW
97
        super(owner, args);
×
98

NEW
99
        window.GoogleFilePickerWidget = this;
×
NEW
100
        this.selectFolder = this.args.selectFolder;
×
NEW
101
        this.mimeTypes = this.args.isFolderPicker ? 'application/vnd.google-apps.folder' : '';
×
NEW
102
        this.parentId = this.args.isFolderPicker ? '': this.args.rootFolderId;
×
NEW
103
        this.title = this.args.isFolderPicker ?
×
104
            this.intl.t('addons.configure.google-file-picker.root-folder-title') :
105
            this.intl.t('addons.configure.google-file-picker.file-folder-title');
NEW
106
        this.isMultipleSelect = !this.args.isFolderPicker;
×
NEW
107
        this.isFolderPicker = this.args.isFolderPicker;
×
108

109

NEW
110
        this.folderName = this.args.selectedFolderName;
×
111

NEW
112
        taskFor(this.loadOauthToken).perform();
×
113
    }
114

115
    @task
116
    @waitFor
117
    private async loadOauthToken(): Promise<void>{
NEW
118
        if (this.args.accountId) {
×
NEW
119
            const authorizedStorageAccount = await this.store.
×
120
                findRecord('authorized-storage-account', this.args.accountId);
NEW
121
            authorizedStorageAccount.serializeOauthToken = true;
×
NEW
122
            const token = await authorizedStorageAccount.save();
×
NEW
123
            this.accessToken = token.oauthToken;
×
NEW
124
            this.isGFPDisabled = this.accessToken ? false : true;
×
125
        }
126
    }
127

128
    /**
129
     * filePickerCallback
130
     *
131
     * @description
132
     * Action triggered when a file is selected via an external picker.
133
     * Logs the file data and notifies the parent system by calling `selectFolder`.
134
     *
135
     * @param file - The file object selected (format determined by external API)
136
     */
137
    @action
138
    filePickerCallback(data: any) {
NEW
139
        if (this.selectFolder !== undefined) {
×
NEW
140
            this.folderName = data.name;
×
NEW
141
            this.selectFolder({
×
142
                itemName: data.name,
143
                itemId: data.id,
144
            });
145
        } else {
NEW
146
            this.args.manager.reload();
×
147
        }
148
    }
149

150
    @action
151
    registerComponent() {
NEW
152
        if (this.args.onRegisterChild) {
×
NEW
153
            this.args.onRegisterChild(this); // Pass the child's instance to the parent
×
154
        }
155
    }
156

157
    willDestroy() {
NEW
158
        super.willDestroy();
×
NEW
159
        this.pickerInited = false;
×
160
    }
161

162

163
    /**
164
    * Callback after api.js is loaded.
165
    */
166
    gapiLoaded() {
NEW
167
        window.gapi.load('client:picker', this.initializePicker.bind(this));
×
168
    }
169

170
    /**
171
    * Callback after the API client is loaded. Loads the
172
    * discovery doc to initialize the API.
173
    */
174
    async initializePicker() {
NEW
175
        this.pickerInited = true;
×
NEW
176
        if (this.isFolderPicker) {
×
NEW
177
            this.visible = true;
×
178
        }
179
    }
180

181
    /**
182
    *  Create and render a Picker object for searching images.
183
    */
184
    @action
185
    createPicker() {
NEW
186
        const googlePickerView = new window.google.picker.DocsView(window.google.picker.ViewId.DOCS);
×
NEW
187
        googlePickerView.setSelectFolderEnabled(true);
×
NEW
188
        googlePickerView.setMimeTypes(this.mimeTypes);
×
NEW
189
        googlePickerView.setIncludeFolders(true);
×
NEW
190
        googlePickerView.setParent(this.parentId);
×
191

NEW
192
        const picker = new window.google.picker.PickerBuilder()
×
193
            .enableFeature(this.isMultipleSelect ? window.google.picker.Feature.MULTISELECT_ENABLED : '')
×
194
            .setDeveloperKey(this.apiKey)
195
            .setAppId(this.appId)
196
            .addView(googlePickerView)
197
            .setTitle(this.title)
198
            .setOAuthToken(this.accessToken)
199
            .setCallback(this.pickerCallback.bind(this))
200
            .build();
NEW
201
        picker.setVisible(true);
×
202
    }
203

204
    /**
205
    * Displays the file details of the user's selection.
206
    * @param {object} data - Containers the user selection from the picker
207
    */
208
    async pickerCallback(data: any) {
NEW
209
        if (data.action === window.google.picker.Action.PICKED) {
×
NEW
210
            this.filePickerCallback(data.docs[0]);
×
211
        }
212
    }
213
}
214

215

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc