Skip to content

Commit

Permalink
Separate SupersetClient and SupersetClientClass (#53)
Browse files Browse the repository at this point in the history
feat: Export `SupersetClientClass`
internal: Increase test coverage.
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 25, 2021
1 parent 4daffd8 commit 6598c75
Show file tree
Hide file tree
Showing 7 changed files with 769 additions and 715 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build:esm": "NODE_ENV=production beemo babel ./src --out-dir esm/ --esm --minify --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
"build:ts": "NODE_ENV=production beemo typescript --workspaces=\"@superset-ui/(connection|chart)\"",
"lint": "beemo create-config prettier && beemo eslint \"./packages/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx}\"",
"lint:fix": "beemo create-config prettier && beemo eslint --fix \"./packages/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx}\"",
"jest": "beemo jest --color --coverage --react",
"postrelease": "lerna run gh-pages",
"prepare-release": "git checkout master && git pull --rebase origin master && lerna bootstrap && yarn run test",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,206 +1,21 @@
import callApi from './callApi';
import {
ClientTimeout,
Credentials,
Headers,
Host,
Mode,
SupersetClientResponse,
RequestConfig,
} from './types';
import { ClientConfig, SupersetClientClass } from './SupersetClientClass';
import { RequestConfig } from './types';

type CsrfToken = string;
type CsrfPromise = Promise<string | undefined>;
type Protocol = 'http:' | 'https:';
let singletonClient: SupersetClientClass | undefined;

export interface ClientConfig {
credentials?: Credentials;
csrfToken?: CsrfToken;
headers?: Headers;
host?: Host;
protocol?: Protocol;
mode?: Mode;
timeout?: ClientTimeout;
}

class SupersetClient {
credentials: Credentials;
csrfToken?: CsrfToken;
csrfPromise?: CsrfPromise;
protocol: Protocol;
host: Host;
headers: Headers;
mode: Mode;
timeout: ClientTimeout;

constructor({
protocol = 'http:',
host = 'localhost',
headers = {},
mode = 'same-origin',
timeout,
credentials = undefined,
csrfToken = undefined,
}: ClientConfig = {}) {
this.headers = { ...headers };
this.host = host;
this.mode = mode;
this.timeout = timeout;
this.protocol = protocol;
this.credentials = credentials;
this.csrfToken = csrfToken;
this.csrfPromise = undefined;

if (typeof this.csrfToken === 'string') {
this.headers = { ...this.headers, 'X-CSRFToken': this.csrfToken };
this.csrfPromise = Promise.resolve(this.csrfToken);
}
}

init(force: boolean = false) {
if (this.isAuthenticated() && !force) {
return this.csrfPromise;
}

return this.getCSRFToken();
}

isAuthenticated() {
// if CSRF protection is disabled in the Superset app, the token may be an empty string
return this.csrfToken !== null && this.csrfToken !== undefined;
}

async get({
body,
credentials,
headers,
host,
endpoint,
mode,
parseMethod,
signal,
timeout,
url,
}: RequestConfig): Promise<SupersetClientResponse> {
return this.ensureAuth().then(() =>
callApi({
body,
credentials: credentials || this.credentials,
headers: { ...this.headers, ...headers },
method: 'GET',
mode: mode || this.mode,
parseMethod,
signal,
timeout: timeout || this.timeout,
url: this.getUrl({ endpoint, host, url }),
}),
);
}

async post({
credentials,
endpoint,
headers,
host,
mode,
parseMethod,
postPayload,
signal,
stringify,
timeout,
url,
}: RequestConfig): Promise<SupersetClientResponse> {
return this.ensureAuth().then(() =>
callApi({
credentials: credentials || this.credentials,
headers: { ...this.headers, ...headers },
method: 'POST',
mode: mode || this.mode,
parseMethod,
postPayload,
signal,
stringify,
timeout: timeout || this.timeout,
url: this.getUrl({ endpoint, host, url }),
}),
);
}

ensureAuth() {
return (
this.csrfPromise ||
Promise.reject({
error: `SupersetClient has no CSRF token, ensure it is initialized or
try logging into the Superset instance at ${this.getUrl({
endpoint: '/login',
})}`,
})
);
}

async getCSRFToken() {
this.csrfToken = undefined;

// If we can request this resource successfully, it means that the user has
// authenticated. If not we throw an error prompting to authenticate.
this.csrfPromise = callApi({
credentials: this.credentials,
headers: {
...this.headers,
},
method: 'GET',
mode: this.mode,
timeout: this.timeout,
url: this.getUrl({ endpoint: 'superset/csrf_token/' }),
}).then(response => {
if (typeof response.json === 'object') {
this.csrfToken = response.json.csrf_token;
if (typeof this.csrfToken === 'string') {
this.headers = { ...this.headers, 'X-CSRFToken': this.csrfToken };
}
}

if (!this.isAuthenticated()) {
return Promise.reject({ error: 'Failed to fetch CSRF token' });
}

return Promise.resolve(this.csrfToken);
});

return this.csrfPromise;
}

getUrl({
host: inputHost,
endpoint = '',
url,
}: {
endpoint?: string;
host?: Host;
url?: string;
} = {}) {
if (typeof url === 'string') return url;

const host = inputHost || this.host;
const cleanHost = host.slice(-1) === '/' ? host.slice(0, -1) : host; // no backslash

return `${this.protocol}//${cleanHost}/${endpoint[0] === '/' ? endpoint.slice(1) : endpoint}`;
}
}

let singletonClient: SupersetClient | undefined;

function hasInstance(maybeClient: SupersetClient | undefined): maybeClient is SupersetClient {
function hasInstance(
maybeClient: SupersetClientClass | undefined,
): maybeClient is SupersetClientClass {
if (!maybeClient) {
throw new Error('You must call SupersetClient.configure(...) before calling other methods');
}

return true;
}

const PublicAPI = {
configure: (config: ClientConfig = {}): SupersetClient => {
singletonClient = new SupersetClient(config);
const SupersetClient = {
configure: (config: ClientConfig = {}): SupersetClientClass => {
singletonClient = new SupersetClientClass(config);

return singletonClient;
},
Expand All @@ -214,6 +29,4 @@ const PublicAPI = {
},
};

export { SupersetClient };

export default PublicAPI;
export default SupersetClient;
Loading

0 comments on commit 6598c75

Please sign in to comment.