Skip to content

Commit

Permalink
Merge pull request #25 from EyeSeeTea/development
Browse files Browse the repository at this point in the history
Release 0.1.0
  • Loading branch information
adrianq authored Feb 4, 2020
2 parents 954e62b + ff54444 commit b779e8c
Show file tree
Hide file tree
Showing 23 changed files with 12,292 additions and 1,743 deletions.
2 changes: 1 addition & 1 deletion .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ module.exports = {
rangeEnd: Infinity,
proseWrap: "preserve",
requirePragma: false,
insertPragma: false
insertPragma: false,
};
143 changes: 138 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# d2-api

Typescript library for DHIS2 api
Typescript library for the DHIS2 API.

## Generate schesmas
## Generate schemas

```
$ yarn generate-schemas http://admin:district@localhost:8080
$ yarn generate-schemas https://admin:district@play.dhis2.org/2.30
```

## Development
Expand All @@ -22,8 +22,24 @@ On your app:
$ yarn link d2-api
```

## Publish

```
$ yarn build
$ yarn publish [--tag beta] [--patch | --minor | --major]
```

## Usage

### Create API instance

```
const api = new D2Api({
baseUrl: "https://play.dhis2.org/2.30",
auth: { username: "admin", password: "district" },
});
```

### Metadata models

#### GET (list)
Expand All @@ -43,9 +59,10 @@ const { cancel, response } = api.models.dataSets.get({
code: { $like: "DS_" },
},
order: "name:asc",
paging: false,
});
console.log({ cancel, data: (await response).data });
console.log({ cancel, data: (await response).data.objects[0].name });
```

#### POST (create)
Expand Down Expand Up @@ -85,7 +102,7 @@ const { cancel, response } = api.metadata.get({
fields: {
id: true,
name: true,
categoryOptions: {
organisationUnits: {
id: true,
name: true,
},
Expand Down Expand Up @@ -114,4 +131,120 @@ const { cancel, response } = api.metadata.post({
periodType: "Monthly",
}],
});
console.log((await response).data)
```

### Analytics

#### Get

```
const analyticsData = await api.analytics
.get({
dimension: ["dx:fbfJHSPpUQD;cYeuwXTCPkU"],
filter: ["pe:2014Q1;2014Q2", "ou:O6uvpzGd5pu;lc3eMKXaEfw"],
})
.getData();
```

#### Run analytics

```
const analyticsRunResponse = await api.analytics.run().getData();
```

### Data values

```
const response = await api.dataValues
.postSet({
dataSet: "Gs69Uw2Mom1",
orgUnit: "qYIeuQe9OwF",
period: "202001",
attributeOptionCombo: "yi2bV1K4vl6",
dataValues: _[
{
dataElement: "a4bd432446",
categoryOptionCombo: "d1bd43245af",
value: "1.5",
},
{
dataElement: "1agd43f4q2",
categoryOptionCombo: "aFwdq324132",
value: "Some comment",
}
],
})
.getData();
```

### Data store

#### Get

```
const dataStore = api.dataStore("namespace1");
const value = await dataStore.get("key1").getData();
```

#### Save

```
const dataStore = api.dataStore("namespace1");
dataStore.save("key1", {x: 1, y: 2});
```

## Using type helpers

_d2-api_ exposes some type helpers that you may need in your app. Some examples:

- `SelectedPick`: Get model from a selector:

```
type PartialUser = SelectedPick<
D2UserSchema,
{
id: true;
favorite: true,
}
>;
// type PartialUser = {id: string, favorite: boolean}
```

- `MetadataPick`: Get indexes models from a metadata selector.

```
type Metadata = MetadataPick<{
users: { fields: { id: true; favorite: true } };
categories: { fields: { id: true; code: true } };
}>;
// type Metadata = {users: {id: string, favorite: boolean}, categories: {id: string, code: string}}
```

## Testing

```
import { getMockApi, D2Api, D2User } from "d2-api";
const currentUserMock = {
id: "xE7jOejl9FI",
displayName: "John Traore",
};
const { api, mock } = getMockApi();
describe("Project", () => {
beforeEach(() => {
mock.reset();
});
describe("getList", () => {
it("returns list of dataSets filtered", async () => {
mock.onGet("/me").reply(200, currentUserMock);
const currentUser = await api.currrentUser.get().getData();
expect(currentUser.id).toEqual("xE7jOejl9FI");
});
});
});
```
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"scripts": {
"start": "NODE_ENV=development babel-watch src --extensions \".js,.jsx,.ts,.tsx\"",
"clean": "rimraf build",
"prebuild": "yarn clean",
"build": "babel src --out-dir build --extensions \".js,.jsx,.ts,.tsx\" && tsc && cp package.json build/",
"build-babel": "babel src --out-dir build --extensions \".js,.jsx,.ts,.tsx\"",
"build": "yarn build-babel && tsc && cp package.json build/",
"predist": "rimraf dist && yarn build",
"dist": "ncc build build/ -m",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"postdist": "cp dist/index.js $npm_package_name.js",
"prettify": "prettier \"{.,src}/**/*.{js,jsx,ts,tsx,json,css}\" --write",
"generate-schemas": "ts-node src/schemas/generator.ts"
Expand All @@ -28,6 +29,7 @@
"argparse": "^1.0.10",
"axios": "^0.19.0",
"axios-debug-log": "^0.6.2",
"axios-mock-adapter": "^1.17.0",
"btoa": "^1.2.1",
"cronstrue": "^1.81.0",
"cryptr": "^4.0.2",
Expand All @@ -38,6 +40,7 @@
"log4js": "^4.5.1",
"node-schedule": "^1.3.2",
"qs": "^6.9.0",
"react": "^16.12.0",
"yargs": "^14.0.0"
},
"devDependencies": {
Expand All @@ -55,16 +58,20 @@
"@types/lodash": "^4.14.144",
"@types/node": "^12.6.3",
"@types/node-schedule": "^1.2.3",
"@types/react": "^16.9.11",
"@types/yargs": "^13.0.2",
"@typescript-eslint/eslint-plugin": "^1.12.0",
"@typescript-eslint/parser": "^1.12.0",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
"@zeit/ncc": "^0.20.4",
"babel-eslint": "^10.0.2",
"babel-watch": "^7.0.0",
"eslint": "^5.12.1",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-react": "^7.17.0",
"prettier": "^1.18.2",
"ts-node": "^8.4.1",
"typescript": "^3.6.4"
"typescript": "^3.6.4",
"watch": "^1.0.2"
}
}
85 changes: 85 additions & 0 deletions src/api/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { D2ApiResponse, HttpResponse } from "./common";
import { D2Api } from "./d2-api";

type Operator = "EQ" | "GT" | "GE" | "LT" | "LE";

export type AnalyticsOptions = {
dimension: string[];
filter?: string[];
aggregationType?:
| "SUM"
| "AVERAGE"
| "AVERAGE_SUM_ORG_UNIT"
| "LAST"
| "LAST_AVERAGE_ORG_UNIT"
| "COUNT"
| "STDDEV"
| "VARIANCE"
| "MIN"
| "MAX";
measureCriteria?: Operator;
preAggregationMeasureCriteria?: Operator;
startDate?: string;
endDate?: string;
skipMeta?: boolean;
skipData?: boolean;
skipRounding?: boolean;
hierarchyMeta?: boolean;
ignoreLimit?: boolean;
tableLayout?: boolean;
hideEmptyRows?: boolean;
hideEmptyColumns?: boolean;
showHierarchy?: boolean;
includeNumDen?: boolean;
includeMetadataDetails?: boolean;
displayProperty?: "NAME" | "SHORTNAME";
outputIdScheme?: string;
inputIdScheme?: string;
approvalLevel?: string;
relativePeriodDate?: string;
userOrgUnit?: string;
columns?: string;
rows?: string;
order?: "ASC" | "DESC";
timeField?: string;
orgUnitField?: string;
};

export type AnalyticsResponse = {
headers: Array<{
name: "dx" | "dy";
column: "Data";
valueType: "TEXT" | "NUMBER";
type: "java.lang.String" | "java.lang.Double";
hidden: boolean;
meta: boolean;
}>;

rows: Array<string[]>;
width: number;
height: number;
};

export type RunAnalyticsResponse = HttpResponse<{
id: string;
created: string;
name: "inMemoryAnalyticsJob";
jobType: "ANALYTICS_TABLE";
jobStatus: "SCHEDULED";
jobParameters: {
skipResourceTables: boolean;
};
relativeNotifierEndpoint: string;
}>;

export default class Analytics {
constructor(public d2Api: D2Api) {}

get(options: AnalyticsOptions): D2ApiResponse<AnalyticsResponse> {
return this.d2Api.get<AnalyticsResponse>("/analytics", options);
}

run(): D2ApiResponse<RunAnalyticsResponse> {
return this.d2Api.post<RunAnalyticsResponse>("/resourceTables/analytics");
}
}
56 changes: 56 additions & 0 deletions src/api/api-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Canceler } from "axios";
import { D2Response } from "./common";

export class D2ApiResponse<Data> {
constructor(public cancel: Canceler, public response: Promise<D2Response<Data>>) {}

static build<Data_>({
cancel,
response,
}: {
cancel?: Canceler;
response: Promise<D2Response<Data_>>;
}): D2ApiResponse<Data_> {
return new D2ApiResponse(cancel || noop, response);
}

getData() {
return this.response.then(({ data }) => data);
}

map<MappedData>(mapper: (response: D2Response<Data>) => MappedData): D2ApiResponse<MappedData> {
const { cancel, response } = this;
const mappedResponse = response.then(
(response_: D2Response<Data>): D2Response<MappedData> => ({
...response_,
data: mapper(response_),
})
);

return new D2ApiResponse<MappedData>(cancel, mappedResponse);
}

flatMap<MappedData>(
mapper: (response: D2Response<Data>) => D2ApiResponse<MappedData>
): D2ApiResponse<MappedData> {
const { cancel, response } = this;
let cancel2: Canceler | undefined;

const mappedResponse = response.then(response_ => {
const res2 = mapper(response_);
cancel2 = res2.cancel;
return res2.response;
});

function cancelAll() {
cancel();
if (cancel2) cancel2();
}

return new D2ApiResponse<MappedData>(cancelAll, mappedResponse);
}
}

const noop = () => {
return;
};
Loading

0 comments on commit b779e8c

Please sign in to comment.