Skip to content

Commit

Permalink
feat: schedule.getEvents (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
miyajan authored Jun 30, 2020
1 parent 4322ca9 commit 6cf5400
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 15 deletions.
29 changes: 29 additions & 0 deletions docs/schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,32 @@ See the example response in the `Reference`.
#### Reference

- https://developer.cybozu.io/hc/ja/articles/360000440583#step1

### getEvents

Get events by specifying conditions.

#### Parameters

| Name | Type | Required | Description |
| ----------------- | :--------------: | :-------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| limit | Number | | The number of events to retrieve.<br />Must be between `1` and `1000`.<br />If nothing is specified, it will default to `100`. |
| offset | Number | | The number of retrievals that will be skipped.<br />Must be between `0` and `2147483647`. If nothing is specified, it will default to `0`. |
| fields | Array\<String\> | | The response properties to get. |
| orderBy | Object | | An object containing data of sort settings. |
| orderBy.property | String | Yes | The property name. Possible values are: `createdAt`, `updatedAt`, `start`. If nothing is specified, it will default to `updatedAt`. |
| orderBy.order | String | Yes | The sort order. Possible values are: `asc`, `desc`. If nothing is specified, it will default to `asc`. |
| rangeStart | String | | The start datetime for the search. The format is RFC3339. (e.g. `2020-01-01T00:00:00Z`)<br />If `rangeEnd` is specified, `rangeStart` must be before the `rangeEnd`. |
| rangeEnd | String | | The end datetime for the search. The format is RFC3339. (e.g. `2020-01-01T00:00:00Z`)<br />If `rangeStart` is specified, `rangeEnd` must be later than `rangeStart`. |
| target | Number or String | Conditionally<br />Required | The ID of an user or an organization or a facility. Required if `targetType` is specified. If nothing is specified, it will default to the login user ID. |
| targetType | String | Conditionally<br />Required | The target type. Possible values are: `user`, `organization`, `facility`. Required if `target` is specified. If nothing is specified, it will default to `user`. |
| keyword | String | Conditionally<br />Required | The search keyword. The keyword is searched for subject, company information, notes and comments. Required if `excludeFromSearch` is specified. |
| excludeFromSearch | Array\<String\> | | The specified elements are excluded from the keyword search. Possible values are: `subject`, `company`, `notes`, `comments`. |

#### Returns

See the example response in the `Reference`.

#### Reference

- https://developer.cybozu.io/hc/ja/articles/360000440583#step2
37 changes: 36 additions & 1 deletion src/client/ScheduleClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HttpClient } from "../http";
import { Event, EventID } from "./types";
import { Event, EventID, ExcludeFromSearchElement, TargetID } from "./types";
import { buildPath } from "../url";

export class ScheduleClient {
Expand All @@ -16,4 +16,39 @@ export class ScheduleClient {
});
return this.client.get(path, {});
}

public getEvents(params?: {
limit?: number;
offset?: number;
fields?: string[];
orderBy?: {
property: "createdAt" | "updatedAt" | "start";
order: "asc" | "desc";
};
rangeStart?: string;
rangeEnd?: string;
target?: TargetID;
targetType?: "user" | "organization" | "facility";
keyword?: string;
excludeFromSearch?: ExcludeFromSearchElement[];
}): Promise<{ events: Event[] }> {
const path = buildPath({ endpointName: "schedule/events" });

if (!params) {
return this.client.get(path, {});
}

const { fields, orderBy, excludeFromSearch, ...rest } = params;
const data: any = rest;
if (fields) {
data.fields = fields.join(",");
}
if (orderBy) {
data.orderBy = `${orderBy.property} ${orderBy.order}`;
}
if (excludeFromSearch) {
data.excludeFromSearch = excludeFromSearch.join(",");
}
return this.client.get(path, data);
}
}
91 changes: 91 additions & 0 deletions src/client/__tests__/ScheduleClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { MockClient } from "../../http/MockClient";
import { ScheduleClient } from "../ScheduleClient";
import { GaroonRequestConfigBuilder } from "../../GaroonRequestConfigBuilder";
import { errorResponseHandler } from "../../GaroonRestAPIClient";

describe("ScheduleClient", () => {
let mockClient: MockClient;
let scheduleClient: ScheduleClient;

beforeEach(() => {
const requestConfigBuilder = new GaroonRequestConfigBuilder({
baseUrl: "https://example.cybozu.com/g",
auth: {
type: "session",
},
});
mockClient = new MockClient({ requestConfigBuilder, errorResponseHandler });
scheduleClient = new ScheduleClient(mockClient);
});

describe("getEvent", () => {
beforeEach(async () => {
await scheduleClient.getEvent({ id: 1 });
});
it("should pass the path to the http client", () => {
expect(mockClient.getLogs()[0].path).toBe("/api/v1/schedule/events/1");
});
it("should send a get request", () => {
expect(mockClient.getLogs()[0].method).toBe("get");
});
it("should pass an empty object as a param to the http client", () => {
expect(mockClient.getLogs()[0].params).toEqual({});
});
});

describe("getEvents", () => {
describe("without parameter", () => {
beforeEach(async () => {
await scheduleClient.getEvents();
});
it("should pass the path to the http client", () => {
expect(mockClient.getLogs()[0].path).toBe("/api/v1/schedule/events");
});
it("should send a get request", () => {
expect(mockClient.getLogs()[0].method).toBe("get");
});
it("should pass an empty object as a param to the http client", () => {
expect(mockClient.getLogs()[0].params).toEqual({});
});
});
describe("with parameter", () => {
beforeEach(async () => {
await scheduleClient.getEvents({
limit: 100,
offset: 0,
fields: ["id", "creator"],
orderBy: {
property: "createdAt",
order: "asc",
},
rangeStart: "2017-10-19T00:10:30Z",
rangeEnd: "2017-10-19T01:10:30Z",
target: 1,
targetType: "user",
keyword: "test",
excludeFromSearch: ["subject", "company"],
});
});
it("should pass the path to the http client", () => {
expect(mockClient.getLogs()[0].path).toBe("/api/v1/schedule/events");
});
it("should send a get request", () => {
expect(mockClient.getLogs()[0].method).toBe("get");
});
it("should pass limit, offset, fields, orderBy, rangeStart, rangeEnd, target, targetType, keyword and excludeFromSearch as a param to the http client", () => {
expect(mockClient.getLogs()[0].params).toEqual({
limit: 100,
offset: 0,
fields: "id,creator",
orderBy: "createdAt asc",
rangeStart: "2017-10-19T00:10:30Z",
rangeEnd: "2017-10-19T01:10:30Z",
target: 1,
targetType: "user",
keyword: "test",
excludeFromSearch: "subject,company",
});
});
});
});
});
6 changes: 6 additions & 0 deletions src/client/types/schedule/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export type EventID = string | number;
export type TargetID = string | number;
export type ExcludeFromSearchElement =
| "subject"
| "company"
| "notes"
| "comments";

export type Event = {
id: string;
Expand Down
36 changes: 24 additions & 12 deletions src/http/HttpClientInterface.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import FormData from "form-data";
export interface HttpClient {
// eslint-disable-next-line @typescript-eslint/ban-types
get: <T extends object>(path: string, params: object) => Promise<T>;
// eslint-disable-next-line @typescript-eslint/ban-types
getData: (path: string, params: object) => Promise<ArrayBuffer>;
// eslint-disable-next-line @typescript-eslint/ban-types
post: <T extends object>(path: string, params: object) => Promise<T>;
// eslint-disable-next-line @typescript-eslint/ban-types
postData: <T extends object>(path: string, params: FormData) => Promise<T>;
// eslint-disable-next-line @typescript-eslint/ban-types
put: <T extends object>(path: string, params: object) => Promise<T>;
// eslint-disable-next-line @typescript-eslint/ban-types
delete: <T extends object>(path: string, params: object) => Promise<T>;
get: <T extends Record<string, unknown>>(
path: string,
params: Record<string, unknown>
) => Promise<T>;
getData: (
path: string,
params: Record<string, unknown>
) => Promise<ArrayBuffer>;
post: <T extends Record<string, unknown>>(
path: string,
params: Record<string, unknown>
) => Promise<T>;
postData: <T extends Record<string, unknown>>(
path: string,
params: FormData
) => Promise<T>;
put: <T extends Record<string, unknown>>(
path: string,
params: Record<string, unknown>
) => Promise<T>;
delete: <T extends Record<string, unknown>>(
path: string,
params: Record<string, unknown>
) => Promise<T>;
}

export type ErrorResponse<T = any> = {
Expand Down
123 changes: 123 additions & 0 deletions src/http/MockClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {
HttpClient,
RequestConfigBuilder,
ErrorResponseHandler,
} from "./HttpClientInterface";
import FormData from "form-data";

type Log = {
method: "get" | "post" | "put" | "delete";
path: string;
params: {
[key: string]: any;
};
};

export class MockClient implements HttpClient {
private readonly errorResponseHandler: ErrorResponseHandler;
private readonly requestConfigBuilder: RequestConfigBuilder;
logs: Log[];
responses: Array<Record<string, unknown>>;

constructor({
errorResponseHandler,
requestConfigBuilder,
}: {
errorResponseHandler: ErrorResponseHandler;
requestConfigBuilder: RequestConfigBuilder;
}) {
this.errorResponseHandler = errorResponseHandler;
this.requestConfigBuilder = requestConfigBuilder;
this.logs = [];
this.responses = [];
}

public mockResponse(mock: Record<string, unknown>) {
this.responses.push(mock);
}
// eslint-disable-next-line @typescript-eslint/ban-types
private createResponse<T extends object>(): T {
const response = this.responses.shift() || {};
if (response instanceof Error) {
this.errorResponseHandler(response);
}
return response as T;
}

public async get<T extends Record<string, unknown>>(
path: string,
params: any
): Promise<T> {
const requestConfig = await this.requestConfigBuilder.build(
"get",
path,
params
);
this.logs.push({ method: requestConfig.method, path, params });
return this.createResponse<T>();
}
public async getData(path: string, params: any): Promise<ArrayBuffer> {
const requestConfig = await this.requestConfigBuilder.build(
"get",
path,
params
);
this.logs.push({ method: requestConfig.method, path, params });
return this.createResponse<ArrayBuffer>();
}
public async post<T extends Record<string, unknown>>(
path: string,
params: any
): Promise<T> {
const requestConfig = await this.requestConfigBuilder.build(
"post",
path,
params
);
this.logs.push({ method: requestConfig.method, path, params });
return this.createResponse<T>();
}
public async postData<T extends Record<string, unknown>>(
path: string,
formData: FormData
): Promise<T> {
const requestConfig = await this.requestConfigBuilder.build(
"post",
path,
formData
);
this.logs.push({
method: requestConfig.method,
path,
params: { formData },
});
return this.createResponse<T>();
}
public async put<T extends Record<string, unknown>>(
path: string,
params: any
): Promise<T> {
const requestConfig = await this.requestConfigBuilder.build(
"put",
path,
params
);
this.logs.push({ method: requestConfig.method, path, params });
return this.createResponse<T>();
}
public async delete<T extends Record<string, unknown>>(
path: string,
params: any
): Promise<T> {
const requestConfig = await this.requestConfigBuilder.build(
"delete",
path,
params
);
this.logs.push({ method: requestConfig.method, path, params });
return this.createResponse<T>();
}
public getLogs(): Log[] {
return this.logs;
}
}
5 changes: 3 additions & 2 deletions src/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ type PlatformDeps = {
) => Promise<{ name: string; data: unknown }>;
getRequestToken: () => Promise<string>;
getDefaultAuth: () => DiscriminatedAuth;
// eslint-disable-next-line @typescript-eslint/ban-types
buildPlatformDependentConfig: (params: object) => object;
buildPlatformDependentConfig: (
params: Record<string, unknown>
) => Record<string, unknown>;
buildHeaders: () => Record<string, string>;
buildFormDataValue: (data: unknown) => unknown;
buildBaseUrl: (baseUrl?: string) => string;
Expand Down

0 comments on commit 6cf5400

Please sign in to comment.