Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:support for management-sdk and axios adapter #130

Merged
merged 10 commits into from
Nov 13, 2024
42 changes: 19 additions & 23 deletions __test__/uiLocation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
LocationType,
Region,
} from "../src/types";
import { ApiRequestProps } from '../src/types/stack.types';
import { dispatchPostRobotRequest } from '../src/utils/adapter';
import { onData, onError } from '../src/utils/utils';
import { RequestOption } from '../src/types/common.types';
import { ApiRequestParams } from '../src/types/api.type';

jest.mock("post-robot");
jest.mock("wolfy87-eventemitter");
Expand Down Expand Up @@ -136,41 +135,35 @@ describe("UI Location", () => {

describe('dispatchPostRobotRequest', () => {
let mockPostRobot: typeof postRobot;
let opts: ApiRequestProps;
let opts: RequestOption;
let uiLocationInstance: UiLocation;
let onError: jest.Mock;

beforeEach(() => {
mockPostRobot = postRobot;
opts = { method: 'GET', url: '/test' };
opts = { method: 'GET' };
uiLocationInstance = new UiLocation(initData);
onError = jest.fn();
uiLocationInstance.api = jest.fn().mockResolvedValue({
method: 'GET',
url: '/test?limit=10&skip=0',
body: {}
url: "https://test.com/test?limit=10&skip=0"
});
});

it('should call sendToParent with the correct arguments and resolve with data', async () => {
const mockData = { success: true };
// Call the method that uses uiLocationInstance.api
const result = await uiLocationInstance.api({
method: 'GET',
url: '/test?limit=10&skip=0',
body: {}
const result = await uiLocationInstance.api("https://test.com/test?limit=10&skip=0",{
method: 'GET'
});

// Assertions
expect(uiLocationInstance.api).toHaveBeenCalledWith({
method: 'GET',
url: '/test?limit=10&skip=0',
body: {}
expect(uiLocationInstance.api).toHaveBeenCalledWith('https://test.com/test?limit=10&skip=0',{
method: 'GET'
});
expect(result).toEqual({
method: 'GET',
url: '/test?limit=10&skip=0',
body: {}
url: 'https://test.com/test?limit=10&skip=0',
});

});
Expand All @@ -187,28 +180,27 @@ describe("UI Location", () => {
});

// Call the method that uses uiLocationInstance.api and expect it to throw an error
await expect(uiLocationInstance.api({
method: 'GET',
url: '/test?limit=10&skip=0',
body: {}
await expect(uiLocationInstance.api("https://test.com/test?limit=10&skip=0",{
method: 'GET'
})).rejects.toThrow('Test error');
});
});


describe("createSDKAdapter", () => {
let mockPostRobot: typeof postRobot;
let opts: ApiRequestProps;
let opts: ApiRequestParams;
let uiLocationInstance: UiLocation;
let onError: jest.Mock;
beforeEach(() => {
mockPostRobot = postRobot;
opts = { method: 'GET', url: '/test' };
opts = { method: 'GET', baseURL:"https://test.com", url:"/test?limit10&skip=0" };
uiLocationInstance = new UiLocation(initData);
onError = jest.fn();
uiLocationInstance.createAdapter = jest.fn().mockResolvedValue({
method: 'GET',
url: '/test?limit=10&skip=0',
baseURL: 'https://test.com',
data: {}
});
});
Expand All @@ -228,18 +220,21 @@ describe("UI Location", () => {
const result = await uiLocationInstance.createAdapter({
method: 'GET',
url: '/test?limit=10&skip=0',
baseURL: 'https://test.com',
data: {}
});

// Assertions
expect(uiLocationInstance.createAdapter).toHaveBeenCalledWith({
method: 'GET',
url: '/test?limit=10&skip=0',
baseURL: 'https://test.com',
data: {}
});
expect(result).toEqual({
method: 'GET',
url: '/test?limit=10&skip=0',
baseURL: 'https://test.com',
data: {}
});
})
Expand All @@ -259,6 +254,7 @@ describe("UI Location", () => {
await expect(uiLocationInstance.createAdapter({
method: 'GET',
url: '/test?limit=10&skip=0',
baseURL: 'https://test.com',
data: {}
})).rejects.toThrow('Test error');
})
Expand Down
1 change: 0 additions & 1 deletion src/stack/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import Query from './query';
import { transform, addParam } from '../utils';
import { dispatchPostRobotRequest } from "../../utils/adapter.ts";
import { ApiRequestProps } from '../../types/stack.types';


function onData(data: { data: any; }) {
Expand Down
18 changes: 18 additions & 0 deletions src/types/api.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HTTPMethods } from './common.types';

export interface ApiRequestParams {
url: string;
method: HTTPMethods;
baseURL?: string;
headers?: Record<string, string>;
data?: unknown;
[key: string]: any;
}

export interface ApiResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
request?: any;
}
36 changes: 0 additions & 36 deletions src/types/axios.type.ts

This file was deleted.

16 changes: 6 additions & 10 deletions src/types/common.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ type _HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTI

export type HTTPMethods = Uppercase<_HTTPMethods> | Lowercase<_HTTPMethods>

export type ApiRequestProps = {
export type RequestOption = {
method: HTTPMethods,
url: string,
headers?: GenericObjectType,
body?: unknown,
Amitkanswal marked this conversation as resolved.
Show resolved Hide resolved
params?:GenericObjectType
multipart_data?: {
[key:string]: {
"type": string,
"value": any
}
}
}
[key:string]: any
}
export type RequestHandler = {
url: string,
} & RequestOption
20 changes: 2 additions & 18 deletions src/types/stack.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Entry from "../entry";
import { AnyProperty, GenericObjectType } from "./common.types";
import { AnyProperty, GenericObjectType, HTTPMethods } from "./common.types";

export declare interface StackDetail {
created_at: string;
Expand Down Expand Up @@ -150,20 +150,4 @@ export interface PublishDetails extends AnyProperty {
locales: Array<String>;
publish_with_reference: boolean;
rules: GenericObjectType;
}

export type RequestMethods = "GET" | "POST" | "PUT" | "DELETE";

export type ApiRequestProps = {
method: RequestMethods,
url: string,
headers?: GenericObjectType,
body?: unknown,
params?:GenericObjectType
multipart_data?: {
[key:string]: {
"type": string,
"value": any
}
}
}
}
28 changes: 14 additions & 14 deletions src/uiLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ import {
Manifest,
Region,
} from "./types";
import { GenericObjectType } from "./types/common.types";
import { GenericObjectType, RequestOption } from "./types/common.types";
import { User } from "./types/user.types";
import { formatAppRegion, onData, onError } from "./utils/utils";
import Window from "./window";
import { ApiRequestProps } from './types/stack.types';
import { createSDKAdapter, dispatchPostRobotRequest } from './utils/adapter';
import { AxiosRequestConfig, AxiosResponse } from './types/axios.type';
import {ApiRequestParams, ApiResponse } from './types/api.type';

const emitter = new EventEmitter();

Expand Down Expand Up @@ -78,13 +77,6 @@ class UiLocation {
*/
stack: Stack;

api: (payload:ApiRequestProps)=> Promise<GenericObjectType>;

/**
* This holds the instance of the createAdapter method that provides ability to app-sdk to integrate javascript management sdk.
*/
createAdapter: (config: AxiosRequestConfig) => Promise<AxiosResponse<GenericObjectType>>;

/**
* Store to persist data for the app.
* Note: Data is stored in the browser's {@link external:localStorage} and will be lost if the {@link external:localStorage} is cleared in the browser.
Expand Down Expand Up @@ -157,10 +149,6 @@ class UiLocation {
currentBranch: initializationData.currentBranch,
});

this.api = (payload:ApiRequestProps)=> dispatchPostRobotRequest(postRobot, payload);

this.createAdapter = createSDKAdapter(postRobot);
Amitkanswal marked this conversation as resolved.
Show resolved Hide resolved

this.metadata = new Metadata(postRobot);

this.config = initializationData.config ?? {};
Expand Down Expand Up @@ -455,6 +443,18 @@ class UiLocation {
return this.region;
};

/**
* Method used to make an API request to the Contentstack's CMA APIs.
*/

api = (url: string, option?: RequestOption) => dispatchPostRobotRequest(this.postRobot)(url, option );

/**
* Method used to create an adapter for management sdk.
*/

createAdapter = <T>(config: unknown) => createSDKAdapter(this.postRobot)(config as unknown as ApiRequestParams) as Promise<ApiResponse<T>>;

/**
* Method used to initialize the App SDK.
* @param version - Version of the app SDK in use.
Expand Down
78 changes: 35 additions & 43 deletions src/utils/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,37 @@
import PostRobot from "post-robot";
import { onData, onError } from "./utils";import { AxiosRequestConfig, AxiosResponse } from '../types/axios.type';
import { ApiRequestProps, GenericObjectType } from '../types/common.types';
import PostRobot from 'post-robot';
import { onData, onError } from './utils';
import { ApiRequestParams } from '../types/api.type';
Amitkanswal marked this conversation as resolved.
Show resolved Hide resolved
import { RequestOption, GenericObjectType } from '../types/common.types';

export function createSDKAdapter(postRobot: typeof PostRobot): (config: AxiosRequestConfig) => Promise<AxiosResponse<GenericObjectType>> {
return async (config: AxiosRequestConfig): Promise<AxiosResponse<GenericObjectType>> => {
const req: ApiRequestProps = {
url: config?.url,
method: config?.method,
headers: config?.headers,
body: config?.data,
params: config?.params,
};

try {
const data = await dispatchPostRobotRequest(postRobot, req) as GenericObjectType;
return {
data,
status: data.status || 200,
statusText: 'OK',
headers: config.headers || {},
config,
request: req,
};
} catch (error) {
const typedError = error as GenericObjectType & { status?: number; statusText?: string; headers?: Record<string, string> };
return {
data: typedError,
status: typedError.status || 500,
statusText: typedError.statusText || 'Internal Server Error',
headers: typedError.headers || {},
config,
request: req,
};
}
};
}
export const dispatchPostRobotRequest = (postRobot: typeof PostRobot) => (url:string ,opts?: RequestOption): Promise<GenericObjectType> => {
return postRobot
.sendToParent("apiAdapter", {url, option:opts})
.then(onData)
.catch(onError);
};

export function dispatchPostRobotRequest(postRobot: typeof PostRobot, opts:ApiRequestProps):Promise<any> {
return postRobot
.sendToParent("apiAdapter", opts)
.then(onData)
.then((data) => data)
.catch(onError);
}
export const createSDKAdapter = (postRobot: typeof PostRobot) => async (config: ApiRequestParams) => {
try {
const data = await dispatchPostRobotRequest(postRobot)(config.url, {
baseURL: config.baseURL,
url: config.url,
method: config.method,
headers: config.headers,
body: config.data as BodyInit,
});
return {
data,
status: data?.status || 200,
statusText: 'OK',
headers: config.headers || {},
};
} catch (error) {
const typedError = error as GenericObjectType & { status?: number; statusText?: string; headers?: Record<string, string>; body?: any; message?: string };
return {
data: typedError.body || typedError.message || typedError.data,
status: typedError.status || 500,
statusText: typedError.statusText || 'Internal Server Error',
headers: typedError.headers || {},
};
}
};
Loading