0.2.16 • Published 5 years ago

@bringg/dashboard-sdk-pre v0.2.16

Weekly downloads
1
License
ISC
Repository
-
Last release
5 years ago

codecov

BRINGG DASHBOARD SDK

Examples:

Web ( >= ES5 )

<!doctype html>
<html lang="en">
<head>
  <!-- ... -->
  <script src="/bringg-dashboard-sdk.js"></script>
</head>
<body>
<!-- ... -->
<script>
BringgDashboardSDK.initWithEmail(email, password).then(bringgDashboardSDK => {
  bringgDashboardSDK.merchant.get().then(console.log).catch(console.error);
  bringgDashboardSDK.merchantConfiguration.get().then(console.log).catch(console.error);
});

BringgDashboardSDK.initWithAuthToken(region, authToken).then(bringgDashboardSDK => {
  bringgDashboardSDK.merchant.get().then(console.log).catch(console.error);
  bringgDashboardSDK.merchantConfiguration.get().then(console.log).catch(console.error);
});
</script>
</body>
</html>

Node.js ( >= ES5 )

const BringgDashboardSDK = require("bringg-dashboard-sdk");

BringgDashboardSDK.initWithEmail(email, password).then(bringgDashboardSDK => {
  bringgDashboardSDK.merchant.get().then(console.log).catch(console.error);
  bringgDashboardSDK.merchantConfiguration.get().then(console.log).catch(console.error);
});

BringgDashboardSDK.initWithAuthToken(region, authToken).then(bringgDashboardSDK => {
  bringgDashboardSDK.merchant.get().then(console.log).catch(console.error);
  bringgDashboardSDK.merchantConfiguration.get().then(console.log).catch(console.error);
});

TypeScript ( >= 2.6 )

import BringgDashboardSDK = require("bringg-dashboard-sdk");

async function runAwait() {
  try {
    const bringgDashboardSDK = await BringgDashboardSDK.initWithEmail(email, password);
    console.log(await bringgDashboardSDK.merchant.get());
    console.log(await bringgDashboardSDK.merchantConfiguration.get());
  } catch (e) {
    console.error(e);
  }

  try {
    const bringgDashboardSDK = await BringgDashboardSDK.initWithAuthToken(region, authToken);
    console.log(await bringgDashboardSDK.merchant.get());
    console.log(await bringgDashboardSDK.merchantConfiguration.get());
  } catch (e) {
    console.error(e);
  }
}

runAwait();

Running tests

yarn test

Running test in watch mode

yarn jest

Linting your code with tsLint

yarn lint

Fixing your code with prettier

yarn prettier

Linting your code and fixing it with one command(will run lint and prettier)

yarn fix

Compile your awesome code for production

yarn prod

DashboardSDK - Guidelines to writing new entities - 10/4/19

Each entity should have 3 parts - Public API, Entity Store, Http Service.

Previous Status

For example we want to expose VehicleType to our end users, we go to the sdk and first add Actions folder.

export enum VehicleTypesActionTypes {
  GET_VEHICLE_TYPES = 'GET_VEHICLE_TYPES',
  MARK_VEHICLE_TYPES_FETCHED = 'MARK_VEHICLE_TYPES_FETCHED',
  SAVE_REQUEST_ERROR = 'SAVE_REQUEST_ERROR',
}

export const Routes = {
  GET_VEHICLE_TYPES: new Route('/vehicle_types', HttpMethod.GET),
};

export function storeVehicleTypesAction(vehicleTypes: VehicleType[]): Actions<VehicleTypesActionTypes> {
  return {
    type: VehicleTypesActionTypes.GET_VEHICLE_TYPES,
    payload: vehicleTypes,
  };
}

export function markVehicleTypesFetchedAction(isFetched: boolean): Actions<VehicleTypesActionTypes> {
  return {
    type: VehicleTypesActionTypes.MARK_VEHICLE_TYPES_FETCHED,
    payload: isFetched,
  };
}

export function saveRequestErrorAction(requestType: VehicleTypesRequests, error: Error): Actions<VehicleTypesActionTypes> {
  return {
    type: VehicleTypesActionTypes.SAVE_REQUEST_ERROR,
    payload: {
      requestType,
      error,
    },
  };
}

export const vehicleTypessExtractor = (response: VehicleTypesResponse) => response.vehicle_types;

export const getVehicleTypesRequestAction = (sessionService: SessionBasedService): any => async (dispatch): Promise<Actions<VehicleTypesActionTypes>> => {
  {
    try {
      const vehicleTypes: VehicleType[] = await sessionService.handleAuthorizedRequest<VehicleTypesResponse, VehicleType[]>(
        Routes.GET_VEHICLE_TYPES,
        vehicleTypessExtractor,
        BringgException.serviceException('failed to get vehicles'),
      );

      dispatch(markVehicleTypesFetchedAction(true));

      return await dispatch(storeVehicleTypesAction(vehicleTypes));
    } catch (e) {
      await dispatch(saveRequestErrorAction(VehicleTypesRequests.GET_VEHICLE_TYPES, e));
      throw e;
    }
  }
};

We continue to next folder Reducers.

export const initState = fromJS({
  items: {},
  requests: {},
  fetched: false,
});

type requestErrorLog = requestErrors<VehicleTypesRequests>;

export type vehicleTypesState = ImmutableMap<{
  items: Map<number, VehicleType>;
  requests: ImmutableMap<requestErrorLog>;
  fetched: boolean;
}>;

export const storeVehicleTypes = (state: vehicleTypesState, vehicleTypes: VehicleType[]): vehicleTypesState =>
  state.set('items', immutableKeyBy(vehicleTypes, 'id'));

export const markVehicleTypesFetched = (state: vehicleTypesState, isFetched: boolean = true) => state.setIn(['fetched'], isFetched);

export const logRequestError = (state, requestType: VehicleTypesRequests, error: Error) => {
  return state.withMutations(state =>
    state.setIn(['requests', requestType, 'error'], error.toString()).setIn(['requests', requestType, 'timestamp'], Date.now()),
  );
};

export const vehicleTypesReducers = (state: vehicleTypesState = initState, action): vehicleTypesState => {
  switch (action.type) {
    case VehicleTypesActionTypes.SAVE_REQUEST_ERROR:
      return logRequestError(state, action.payload.requestType, action.payload.error);
    case VehicleTypesActionTypes.GET_VEHICLE_TYPES:
      return storeVehicleTypes(state, action.payload);
    case VehicleTypesActionTypes.MARK_VEHICLE_TYPES_FETCHED:
      return markVehicleTypesFetched(state, action.payload);
    default:
      return state;
  }
};

Then we go on and add a Service folder.

export default class VehicleTypesService extends SessionBasedService {
  private store: Redux.Store<CompleteStateStructure>;

  constructor(session: Session) {
    super(session.config.getApiEndpoint(), session);
    this.store = session.state.store;
  }

  @singleConcurrent()
  public async getAll(): Promise<VehicleType[]> {
    if (!this.areVehicleTypesFetched()) {
      await this.store.dispatch(getVehicleTypesRequestAction(this));
    }

    return this.getAllVehicleTypes();
  }

  private getAllVehicleTypes = (): VehicleType[] => {
    return this.store
      .getState()
      .vehicleTypes.getIn(['items'])
      .toList()
      .toJS();
  };

  private areVehicleTypesFetched = (): boolean => {
    return this.store.getState().vehicleTypes.getIn(['fetched']);
  };
}

Then we finally can expose our API folder :)

export default class VehicleTypes {
  private vehicleTypesService: VehicleTypesService;

  constructor(session: Session) {
    this.vehicleTypesService = new VehicleTypesService(session);
  }

  public async getAll(): Promise<VehicleType[]> {
    return await this.vehicleTypesService.getAll();
  }
}

Almost 200 hundred lines of code just to expose simply getAll of VehicleTypes.

We tried to simplify the proccess by creating BaseStore and using it in each new entity store instead of rewriting it each time.

Entity Store

We want to keep each entity state up to date always(http requests, realtime events), so we use store managment class to do it.

From now on we can use a new class named BaseStore which will be our base of every entity and manage its state.

For Example:

export default class ChatConversationStore extends BaseStore<ChatConversation>

Here we can extend our BaseStore and use it however we see fit to the new entity we develop(take from store if exists, if not fetch from server and store it).

  async getConversation(id: number): Promise<ChatConversation> {
    const chatConversation = this.getItem(id) || this._fetchConversation(id);

    return await chatConversation;
  }

Http Service

Http service is pretty much the same all over the sdk, here is simple example.

  GET_CHAT_CONVERSATION: new Route('/chat_conversations/{:id}', HttpMethod.GET)

  public async getChatConversation(id): Promise<ChatConversation> {
    const chatConversationResponse:  ChatConversationResponse = await this.handleAuthorizedRequest<ChatConversationResponse, ChatConversationResponse>(
      Routes.GET_CHAT_CONVERSATION, //route
      defaultExtractor, // extractor func can be passed here
      BringgException.serviceException('Failed to get chat conversation'),
      undefined, //payload
      { id }, //route params
      {}, // query params
    );

    return chatConversationResponse.conversation;
  }

Public API

Here we decide which endpoints and events we want to expose to the end user, which will be called from the entity store.

Example from ChatConversation

this.onItemRemoved = this.chatConversationStore.onItemRemoved; // remove event

public getConversation(id: number): Promise<ChatConversation> {
    return this.chatConversationStore.getConversation(id); // get item endpoint
}

BaseStore

Here we implemented RxJS,

we have our main stream of items $items which on each action(create,update,remove) we emit to the stream and accordingly aggregate the most update state.

Each store extends this class can use $items stream to aggregate which data he wants.(for example from tasks stream aggreate only open tasks.. )

  state$: Observable<Map<number, T>>; // aggregated state from main stream
  protected items$: Subject<Action>; // 1 main stream of items

  // Event Handlers
  protected onItemUpdateEvent: (item: T) => void = _.noop;
  protected onItemCreatedEvent: (item: T) => void = _.noop;
  protected onItemRemovedEvent: (item: number) => void = _.noop;
  protected onListUpdateEvent: (items: T[]) => void = _.noop;

  getItem = (itemId): T // Get Item From Store By Id

  addToStore(item: T) // Add item to store

  updateInStore(item: T) // update item in store

  getItems = (): T[] // get array of items of latest state in store

  addItems(items: T[]) // add array of items

  onItemCreated = (fn: (item: T) => void) => // set event handler for item removed

  onItemUpdate = (fn: (item: T) => void) => // set event handler for item updated

  onItemRemoved = (fn: (itemId: number) => void) // set event handler for item removed

  onListUpdate = (fn: (item: T[]) => void) // set event handler for list update

Entities Generator

In order to save time and make your life easier, there is a script to generate the basic implementation needed.

You can run it with : npm run entity-generator name

The script will create a folder called name inside src folder.

The folder will include Service, Store and a few more files with basic implementation and according to our convention.

Next Steps:

In order to improve our SDK and our developing proccess.

  • Combine Store and Service to one.(instead of copy pasting same service all the time)