Skip to content

Commit

Permalink
[chore] Refactored cloud provider flow for performance and multi prov…
Browse files Browse the repository at this point in the history
…ider support (#2436)

Signed-off-by: Giuseppe Macri <gmacri@foursquare.com>
  • Loading branch information
macrigiuseppe committed Nov 17, 2023
1 parent d975ea1 commit 82fc69e
Show file tree
Hide file tree
Showing 50 changed files with 1,737 additions and 1,613 deletions.
20 changes: 10 additions & 10 deletions examples/demo-app/src/app.js
Expand Up @@ -97,6 +97,15 @@ const GlobalStyle = styled.div`
}
`;

const CONTAINER_STYLE = {
transition: 'margin 1s, height 1s',
position: 'absolute',
width: '100%',
height: '100%',
left: 0,
top: 0
};

class App extends Component {
state = {
showBanner: false,
Expand Down Expand Up @@ -416,16 +425,7 @@ class App extends Component {
>
<Announcement onDisable={this._disableBanner} />
</Banner>
<div
style={{
transition: 'margin 1s, height 1s',
position: 'absolute',
width: '100%',
height: '100%',
left: 0,
top: 0
}}
>
<div style={CONTAINER_STYLE}>
<AutoSizer>
{({height, width}) => (
<KeplerGl
Expand Down
232 changes: 118 additions & 114 deletions examples/demo-app/src/cloud-providers/dropbox/dropbox-provider.js
Expand Up @@ -60,7 +60,7 @@ export default class DropboxProvider extends Provider {
this.appName = appName;

this._folderLink = `${KEPLER_DROPBOX_FOLDER_LINK}/${appName}`;
this._path = `/Apps/${window.decodeURIComponent(this.appName)}`;
this._path = '';

// Initialize Dropbox API
this._initializeDropbox();
Expand All @@ -73,71 +73,62 @@ export default class DropboxProvider extends Provider {
* - Receive the token when ready
* - Close the opened tab
*/
async login(onCloudLoginSuccess) {
const link = this._authLink();

const authWindow = window.open(link, '_blank', 'width=1024,height=716');
async login() {
return new Promise((resolve, reject) => {
const link = this._authLink();

const handleToken = async e => {
// TODO: add security step to validate which domain the message is coming from
if (authWindow) {
authWindow.close();
}
const authWindow = window.open(link, '_blank', 'width=1024,height=716');

window.removeEventListener('message', handleToken);
const handleToken = async event => {
// if user has dev tools this will skip all the react-devtools events
if (!event.data.token) {
return;
}

if (!e.data.token) {
Console.warn('Failed to login to Dropbox');
return;
}
if (authWindow) {
authWindow.close();
window.removeEventListener('message', handleToken);
}

this._dropbox.setAccessToken(e.data.token);
// save user name
const user = await this._getUser();

if (window.localStorage) {
window.localStorage.setItem(
'dropbox',
JSON.stringify({
// dropbox token doesn't expire unless revoked by the user
token: e.data.token,
user,
timestamp: new Date()
})
);
}
const {token} = event.data;

if (typeof onCloudLoginSuccess === 'function') {
onCloudLoginSuccess();
}
};

window.addEventListener('message', handleToken);
}
if (!token) {
reject('Failed to login to Dropbox');
return;
}

async downloadMap(loadParams) {
const token = this.getAccessToken();
if (!token) {
this.login(() => this.downloadMap(loadParams));
}
const result = await this._dropbox.filesDownload(loadParams);
const json = await this._readFile(result.fileBlob);
this._dropbox.setAccessToken(token);
// save user name
const user = await this.getUser();

if (window.localStorage) {
window.localStorage.setItem(
'dropbox',
JSON.stringify({
// dropbox token doesn't expire unless revoked by the user
token: token,
user,
timestamp: new Date()
})
);
}

const response = {
map: json,
format: 'keplergl'
};
resolve(user);
};

this._loadParam = loadParams;
return response;
window.addEventListener('message', handleToken);
});
}

/**
* returns a list of maps
*/
async listMaps() {
// list files
try {
// https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#filesListFolder__anchor
const response = await this._dropbox.filesListFolder({
path: this._path
path: `${this._path}`
});
const {pngs, visualizations} = this._parseEntries(response);
// https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#filesGetThumbnailBatch__anchor
Expand All @@ -148,15 +139,14 @@ export default class DropboxProvider extends Provider {
);

// append to visualizations
thumbnails &&
thumbnails.forEach(thb => {
if (thb['.tag'] === 'success' && thb.thumbnail) {
const matchViz = visualizations[pngs[thb.metadata.id] && pngs[thb.metadata.id].name];
if (matchViz) {
matchViz.thumbnail = `${IMAGE_URL_PREFIX}${thb.thumbnail}`;
}
(thumbnails || []).forEach(thb => {
if (thb['.tag'] === 'success' && thb.thumbnail) {
const matchViz = visualizations[pngs[thb.metadata.id] && pngs[thb.metadata.id].name];
if (matchViz) {
matchViz.thumbnail = `${IMAGE_URL_PREFIX}${thb.thumbnail}`;
}
});
}
});

// dropbox returns
return Object.values(visualizations).reverse();
Expand All @@ -166,49 +156,10 @@ export default class DropboxProvider extends Provider {
}
}

getUserName() {
// load user from
if (window.localStorage) {
const jsonString = window.localStorage.getItem('dropbox');
const user = jsonString && JSON.parse(jsonString).user;
return user;
}
return null;
}

async logout(onCloudLogoutSuccess) {
const token = this._dropbox.getAccessToken();

if (token) {
await this._dropbox.authTokenRevoke();
if (window.localStorage) {
window.localStorage.removeItem('dropbox');
}
// re instantiate dropbox
this._initializeDropbox();
onCloudLogoutSuccess();
}
}

isEnabled() {
return this.clientId !== null;
}

hasPrivateStorage() {
return PRIVATE_STORAGE_ENABLED;
}

hasSharingUrl() {
return SHARING_ENABLED;
}

/**
*
* @param mapData map data and config in one json object {map: {datasets: Array<Object>, config: Object, info: Object}
* @param blob json file blob to upload
* @param fileName if blob doesn't contain a file name, this field is used
* @param isPublic define whether the file will be available publicly once uploaded
* @returns {Promise<DropboxTypes.files.FileMetadata>}
*/
async uploadMap({mapData, options = {}}) {
const {isPublic} = options;
Expand All @@ -221,10 +172,11 @@ export default class DropboxProvider extends Provider {
// FileWriteMode: Selects what to do if the file already exists.
// Always overwrite if sharing
const mode = options.overwrite || isPublic ? 'overwrite' : 'add';
const path = `${this._path}/${fileName}`;
let metadata;
try {
metadata = await this._dropbox.filesUpload({
path: `${this._path}/${fileName}`,
path,
contents: JSON.stringify(fileContent),
mode
});
Expand All @@ -233,13 +185,15 @@ export default class DropboxProvider extends Provider {
throw this.getFileConflictError();
}
}

// save a thumbnail image
thumbnail &&
(await this._dropbox.filesUpload({
path: `${this._path}/${fileName}`.replace(/\.json$/, '.png'),
if (thumbnail) {
await this._dropbox.filesUpload({
path: path.replace(/\.json$/, '.png'),
contents: thumbnail,
mode
}));
});
}

// keep on create shareUrl
if (isPublic) {
Expand All @@ -252,6 +206,58 @@ export default class DropboxProvider extends Provider {
return this._loadParam;
}

/**
* download the map content
* @param loadParams
*/
async downloadMap(loadParams) {
const token = this.getAccessToken();
if (!token) {
this.login(() => this.downloadMap(loadParams));
}
const result = await this._dropbox.filesDownload(loadParams);
const json = await this._readFile(result.fileBlob);

const response = {
map: json,
format: 'keplergl'
};

this._loadParam = loadParams;
return response;
}

getUserName() {
// load user from
if (window.localStorage) {
const jsonString = window.localStorage.getItem('dropbox');
const user = jsonString && JSON.parse(jsonString).user;
return user;
}
return null;
}

async logout() {
await this._dropbox.authTokenRevoke();
if (window.localStorage) {
window.localStorage.removeItem('dropbox');
}
// re instantiate dropbox
this._initializeDropbox();
}

isEnabled() {
return this.clientId !== null;
}

hasPrivateStorage() {
return PRIVATE_STORAGE_ENABLED;
}

hasSharingUrl() {
return SHARING_ENABLED;
}

/**
* Get the share url of current map, this url can be accessed by anyone
* @param {boolean} fullUrl
Expand Down Expand Up @@ -314,22 +320,15 @@ export default class DropboxProvider extends Provider {
this._dropbox.setClientId(this.clientId);
}

async _getUser() {
let response;
try {
response = await this._dropbox.usersGetCurrentAccount();
} catch (error) {
Console.warn(error);
return null;
}

async getUser() {
const response = await this._dropbox.usersGetCurrentAccount();
return this._getUserFromAccount(response);
}

_handleDropboxError(error) {
// dropbox list_folder error
if (error && error.error && error.error.error_summary) {
return `Dropbox Error: ${error.error.error_summary}`;
return new Error(`Dropbox Error: ${error.error.error_summary}`);
}

return error;
Expand Down Expand Up @@ -422,7 +421,12 @@ export default class DropboxProvider extends Provider {
}

_getUserFromAccount(response) {
return response ? (response.name && response.name.abbreviated_name) || response.email : null;
const {name} = response;
return {
name: name.display_name,
email: response.email,
abbreviated: name.abbreviated_name
};
}

_getThumbnailRequests(pngs) {
Expand Down
2 changes: 1 addition & 1 deletion examples/demo-app/src/cloud-providers/index.js
Expand Up @@ -24,7 +24,7 @@ import DropboxProvider from './dropbox/dropbox-provider';
import CartoProvider from './carto/carto-provider';

const {DROPBOX_CLIENT_ID, CARTO_CLIENT_ID} = AUTH_TOKENS;
const DROPBOX_CLIENT_NAME = 'Kepler.gl%20(managed%20by%20Uber%20Technologies%2C%20Inc.)';
const DROPBOX_CLIENT_NAME = 'Kepler.gl Demo App';

export const DEFAULT_CLOUD_PROVIDER = 'dropbox';

Expand Down
2 changes: 1 addition & 1 deletion examples/demo-app/src/factories/map-control.js
Expand Up @@ -21,7 +21,7 @@
import React from 'react';
import styled from 'styled-components';
import {
withState,
withState,
MapControlFactory,
EffectControlFactory,
EffectManagerFactory
Expand Down
6 changes: 5 additions & 1 deletion jest.config.js
Expand Up @@ -25,7 +25,11 @@ const config = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
verbose: true,
testMatch: ['<rootDir>/src/**/*.spec.js', '<rootDir>/test/**/*.spec.js'],
testPathIgnorePatterns: [
// ignore all dist computed directories
"<rootDir>/.*(/|\\\\)dist(/|\\\\).*"
],
testMatch: ['<rootDir>/src/**/*.spec.(ts|tsx)', '<rootDir>/src/**/*.spec.js', '<rootDir>/test/**/*.spec.js'],
// Per https://jestjs.io/docs/configuration#transformignorepatterns-arraystring, transformIgnorePatterns ignores
// node_modules and pnp folders by default so that they are not transpiled
// Some libraries (even if transitive) are transitioning to ESM and need additional transpilation. Relevant issues:
Expand Down

0 comments on commit 82fc69e

Please sign in to comment.