Skip to content

Commit b141389

Browse files
authored
Merge pull request #93 from DenisaCG/external_drives
Add support for cross-account drives
2 parents f00a6bb + 398747a commit b141389

File tree

8 files changed

+150
-15
lines changed

8 files changed

+150
-15
lines changed

jupyter_drives/handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ async def post(self, drive: str = "", path: str = ""):
101101
body = self.get_json_body()
102102
if 'location' in body:
103103
result = await self._manager.new_drive(drive, **body)
104-
elif 'public' in body:
105-
result = await self._manager.add_public_drive(drive)
104+
elif 'is_public' in body:
105+
result = await self._manager.add_external_drive(drive, **body)
106106
else:
107107
result = await self._manager.new_file(drive, path, **body)
108108
self.finish(result)

jupyter_drives/manager.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,10 @@ async def mount_drive(self, drive_name, provider):
311311
"""
312312
try:
313313
if provider == 's3':
314-
region = await self._get_drive_location(drive_name)
314+
if drive_name in self._external_drives and self._external_drives[drive_name]["is_public"] is False:
315+
region = self._external_drives[drive_name]["location"]
316+
else:
317+
region = await self._get_drive_location(drive_name)
315318
self._initialize_content_manager(drive_name, provider, region)
316319
except Exception as e:
317320
raise tornado.web.HTTPError(
@@ -731,16 +734,17 @@ async def new_drive(self, new_drive_name, location):
731734

732735
return
733736

734-
async def add_public_drive(self, drive_name):
737+
async def add_external_drive(self, drive_name, is_public, region='us-east-1'):
735738
"""Mount a drive.
736739
737740
Args:
738741
drive_name: name of public bucket to mount
739742
"""
740743
try:
741744
drive = {
742-
"is_public": True,
743-
"url": drive_name
745+
"is_public": is_public,
746+
"url": drive_name,
747+
"location": region
744748
};
745749
self._external_drives[drive_name] = drive;
746750
except Exception as e:

src/contents.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
createDrive,
2525
getDrivesList,
2626
excludeDrive,
27-
includeDrive
27+
includeDrive,
28+
addExternalDrive
2829
} from './requests';
2930

3031
export class Drive implements Contents.IDrive {
@@ -850,6 +851,42 @@ export class Drive implements Contents.IDrive {
850851
return data;
851852
}
852853

854+
/**
855+
* Add external drive.
856+
*
857+
* @param options: The options used to add the external drive.
858+
*
859+
* @returns A promise which resolves with the contents model.
860+
*/
861+
async addExternalDrive(
862+
driveUrl: string,
863+
location: string
864+
): Promise<Contents.IModel> {
865+
await addExternalDrive(driveUrl, location);
866+
867+
const data: Contents.IModel = {
868+
name: driveUrl,
869+
path: driveUrl,
870+
last_modified: '',
871+
created: '',
872+
content: [],
873+
format: 'json',
874+
mimetype: '',
875+
size: 0,
876+
writable: true,
877+
type: 'directory'
878+
};
879+
880+
Contents.validateContentsModel(data);
881+
this._fileChanged.emit({
882+
type: 'new',
883+
oldValue: null,
884+
newValue: data
885+
});
886+
887+
return data;
888+
}
889+
853890
/**
854891
* Exclude drive from browser.
855892
*

src/plugins/driveBrowserPlugin.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,38 @@ namespace Private {
454454
rank: 110
455455
});
456456

457+
app.commands.addCommand(CommandIDs.addExternalDrive, {
458+
isVisible: () => {
459+
return browser.model.path === 's3:';
460+
},
461+
execute: async () => {
462+
return showDialog({
463+
title: 'Add External Drive',
464+
body: new Private.CreateDriveHandler(drive.name),
465+
focusNodeSelector: 'input',
466+
buttons: [
467+
Dialog.cancelButton(),
468+
Dialog.okButton({
469+
label: 'Add',
470+
ariaLabel: 'Add Drive'
471+
})
472+
]
473+
}).then(result => {
474+
if (result.value) {
475+
drive.addExternalDrive(result.value[0], result.value[1]);
476+
}
477+
});
478+
},
479+
label: 'Add External Drive',
480+
icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' })
481+
});
482+
483+
app.contextMenu.addItem({
484+
command: CommandIDs.addExternalDrive,
485+
selector: '#drive-file-browser.jp-SidePanel .jp-DirListing-content',
486+
rank: 110
487+
});
488+
457489
app.commands.addCommand(CommandIDs.toggleFileFilter, {
458490
execute: () => {
459491
// Update toggled state, then let the toolbar button update

src/plugins/drivelistmanager.tsx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Button, Search } from '@jupyter/react-components';
44
import { useState } from 'react';
55
import { IDriveInfo } from '../token';
66
import {
7+
addExternalDrive,
78
addPublicDrive,
89
excludeDrive,
910
getDrivesList,
@@ -19,15 +20,23 @@ interface IProps {
1920

2021
export interface IDriveInputProps {
2122
isName: boolean;
22-
value: string;
23+
driveValue: string;
24+
regionValue: string;
2325
setPublicDrive: (value: string) => void;
26+
setRegion: (value: string) => void;
2427
onSubmit: () => void;
28+
isPublic: boolean;
29+
setIsPublic: (value: boolean) => void;
2530
}
2631

2732
export function DriveInputComponent({
28-
value,
33+
driveValue,
34+
regionValue,
2935
setPublicDrive,
30-
onSubmit
36+
setRegion,
37+
onSubmit,
38+
isPublic,
39+
setIsPublic
3140
}: IDriveInputProps) {
3241
return (
3342
<div>
@@ -38,7 +47,7 @@ export function DriveInputComponent({
3847
setPublicDrive(event.target.value);
3948
}}
4049
placeholder="Enter drive name"
41-
value={value}
50+
value={driveValue}
4251
/>
4352
<Button
4453
className="input-add-drive-button"
@@ -48,6 +57,24 @@ export function DriveInputComponent({
4857
add
4958
</Button>
5059
</div>
60+
<div className="add-public-drive-section">
61+
{'Public drive?'}
62+
<input
63+
type="checkbox"
64+
checked={isPublic}
65+
onChange={event => setIsPublic(event.target.checked)}
66+
/>
67+
{!isPublic && (
68+
<input
69+
className="drive-region-input"
70+
onInput={(event: any) => {
71+
setRegion(event.target.value);
72+
}}
73+
placeholder="Region (e.g.: us-east-1)"
74+
value={regionValue}
75+
/>
76+
)}
77+
</div>
5178
</div>
5279
);
5380
}
@@ -154,6 +181,8 @@ export function DriveListManagerComponent({ model }: IProps) {
154181
const [availableDrives, setAvailableDrives] = useState<Partial<IDriveInfo>[]>(
155182
model.availableDrives
156183
);
184+
const [isPublic, setIsPublic] = useState<boolean>(false);
185+
const [driveRegion, setDriveRegion] = useState<string>('');
157186

158187
// Called after mounting.
159188
React.useEffect(() => {
@@ -168,7 +197,12 @@ export function DriveListManagerComponent({ model }: IProps) {
168197
}, [model]);
169198

170199
const onAddedPublicDrive = async () => {
171-
await addPublicDrive(publicDrive);
200+
if (isPublic) {
201+
await addPublicDrive(publicDrive);
202+
} else {
203+
await addExternalDrive(publicDrive, driveRegion);
204+
setDriveRegion('');
205+
}
172206
setPublicDrive('');
173207
await model.refresh();
174208
};
@@ -195,11 +229,15 @@ export function DriveListManagerComponent({ model }: IProps) {
195229
</div>
196230

197231
<div className="drives-manager-section">
198-
<div className="drives-section-title"> Add public drive</div>
232+
<div className="drives-section-title"> Add external drive</div>
199233
<DriveInputComponent
200234
isName={false}
201-
value={publicDrive}
235+
driveValue={publicDrive}
236+
regionValue={driveRegion}
202237
setPublicDrive={setPublicDrive}
238+
setRegion={setDriveRegion}
239+
isPublic={isPublic}
240+
setIsPublic={setIsPublic}
203241
onSubmit={onAddedPublicDrive}
204242
/>
205243
</div>

src/requests.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,22 @@ export async function createDrive(
509509
*/
510510
export async function addPublicDrive(driveUrl: string) {
511511
return await requestAPI<any>('drives/' + driveUrl + '/', 'POST', {
512-
public: true
512+
is_public: true
513+
});
514+
}
515+
516+
/**
517+
* Add external drive.
518+
*
519+
* @param driveUrl The drive URL.
520+
* @param location The drive region.
521+
*
522+
* @returns A promise which resolves with the contents model.
523+
*/
524+
export async function addExternalDrive(driveUrl: string, location: string) {
525+
return await requestAPI<any>('drives/' + driveUrl + '/', 'POST', {
526+
is_public: false,
527+
region: location
513528
});
514529
}
515530

src/token.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export namespace CommandIDs {
99
export const toggleBrowser = 'drives:toggle-main';
1010
export const createNewDrive = 'drives:create-new-drive';
1111
export const addPublicDrive = 'drives:add-public-drive';
12+
export const addExternalDrive = 'drives:add-external-drive';
1213
export const launcher = 'launcher:create';
1314
export const toggleFileFilter = 'drives:toggle-file-filter';
1415
export const createNewDirectory = 'drives:create-new-directory';

style/base.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ li {
5050
justify-content: center;
5151
}
5252

53+
.drive-region-input {
54+
height: 20px !important;
55+
width: 100%;
56+
flex: 1;
57+
justify-content: center;
58+
margin-right: 61px;
59+
}
60+
5361
.drive-data-grid {
5462
width: 380px;
5563
flex-direction: row;

0 commit comments

Comments
 (0)