Skip to content

Commit

Permalink
feedback: expose export progress
Browse files Browse the repository at this point in the history
  • Loading branch information
bitpshr committed Jan 14, 2020
1 parent 28243ac commit 393b893
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 26 deletions.
13 changes: 10 additions & 3 deletions abstract-sdk.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ interface Descriptors extends Endpoint {
interface Files extends Endpoint {
info(descriptor: FileDescriptor, requestOptions: RequestOptions): Promise<File>;
list(descriptor: BranchCommitDescriptor, requestOptions: RequestOptions): Promise<File[]>;
raw(descriptor: FileDescriptor, options?: RawOptions): Promise<ArrayBuffer | void>;
raw(descriptor: FileDescriptor, options?: RawProgressOptions): Promise<ArrayBuffer | void>;
}

interface Layers extends Endpoint {
Expand Down Expand Up @@ -1851,11 +1851,18 @@ type RequestConfig<T> = {
cli?: () => T
};

type ProgressCallback = (receivedBytes: number, totalBytes: number) => void;

type ApiRequestOptions = {
customHostname?: string,
raw?: boolean
raw?: boolean,
onProgress?: ProgressCallback
};

type RequestOptions = {
transportMode?: ("api" | "cli")[]
};
};

type RawProgressOptions = RawOptions & {
onProgress?: ProgressCallback
};
50 changes: 42 additions & 8 deletions docs/abstract-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ abstract.assets.commit({
});
```

### Retrieve an asset file
### Export an asset file

![API][api-icon]

Expand Down Expand Up @@ -794,11 +794,11 @@ abstract.files.info({
});
```

### Retrieve a Sketch file
### Export a file

![CLI][cli-icon]
![CLI][cli-icon] ![API][api-icon]

`files.raw(FileDescriptor, RawOptions): Promise<ArrayBuffer>`
`files.raw(FileDescriptor, RawProgressOptions): Promise<ArrayBuffer | void>`

Retrieve a Sketch file from Abstract based on its file ID and save it to disk. Files will be saved to the current working directory by default, but a custom `filename` option can be used to customize this location.

Expand All @@ -811,14 +811,38 @@ abstract.files.raw({
});
```

You can also load the file info at any commit on the branch…
The resulting `ArrayBuffer` can be also be used with node `fs` APIs directly. For example, it's possible to write the file to disk manually after post-processing it:

```js
abstract.files.info({
const arrayBuffer = await abstract.files.raw({
projectId: "616daa90-1736-11e8-b8b0-8d1fec7aef78",
branchId: "master",
fileId: "51DE7CD1-ECDC-473C-B30E-62AE913743B7",
sha: "fb7e9b50da6c330fc43ffb369616f0cd1fa92cc2"
fileId: "51DE7CD1-ECDC-473C-B30E-62AE913743B7"
sha: "latest"
}, {
disableWrite: true
});

processedBuffer = postProcess(arrayBuffer);

fs.writeFile("file.sketch", Buffer.from(processedBuffer), (err) => {
if (err) throw err;
console.log("File written!");
});
```

It's also possible to get insight into the underlying progress of the file export.

```js
abstract.files.raw({
projectId: "616daa90-1736-11e8-b8b0-8d1fec7aef78",
branchId: "master",
fileId: "51DE7CD1-ECDC-473C-B30E-62AE913743B7"
sha: "latest"
}, {
onProgress: (receivedBytes: number, totalBytes: number) => {
console.log(`${receivedBytes * 100 / totalBytes}% complete`);
}
});
```

Expand Down Expand Up @@ -1636,6 +1660,16 @@ Options objects that can be passed to different SDK endpoints.
}
```

### RawProgressOptions
```js
{
transportMode?: ("api" | "cli")[],
disableWrite?: boolean,
filename?: string,
onProgress?: (receivedBytes: number, totalBytes: number) => void;
}
```

## Descriptors

Reference for the parameters required to load resources with the Abstract SDK.
Expand Down
33 changes: 32 additions & 1 deletion src/endpoints/Endpoint.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow */
/* global fetch */
import { Readable } from "stream";
import "cross-fetch/polyfill";
import { spawn } from "child_process";
import uuid from "uuid/v4";
Expand Down Expand Up @@ -80,7 +81,7 @@ export default class Endpoint {
fetchOptions: Object = {},
apiOptions: ApiRequestOptions = {}
) {
const { customHostname, raw } = apiOptions;
const { customHostname, raw, onProgress } = apiOptions;
const hostname = customHostname || (await this.options.apiUrl);

fetchOptions.body = fetchOptions.body && JSON.stringify(fetchOptions.body);
Expand All @@ -96,6 +97,36 @@ export default class Endpoint {
return (undefined: any);
}

if (onProgress) {
const totalSize = Number(response.headers.get("Content-Length"));
let receivedSize = 0;
const body = await response.body;
// Node environments using cross-fetch polyfill
/* istanbul ignore else */
if (body instanceof Readable) {
body.on("readable", () => {
let chunk;
while ((chunk = body.read())) {
receivedSize += chunk.length;
onProgress(receivedSize, totalSize);
}
});
// Browser environments using native fetch
} else if (body) {
const reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (value) {
receivedSize += value.length;
onProgress(receivedSize, totalSize);
}
if (done) {
break;
}
}
}
}

// prettier-ignore
const apiValue: any = await (raw ? response.arrayBuffer() : response.json());
const logValue = raw ? apiValue.toString() : apiValue;
Expand Down
9 changes: 5 additions & 4 deletions src/endpoints/Files.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
BranchCommitDescriptor,
File,
FileDescriptor,
RawOptions,
RawProgressOptions,
RequestOptions
} from "../types";
import { FileExportError, NotFoundError } from "../errors";
Expand Down Expand Up @@ -77,8 +77,8 @@ export default class Files extends Endpoint {
});
}

async raw(descriptor: FileDescriptor, options: RawOptions = {}) {
const { disableWrite, filename, ...requestOptions } = options;
async raw(descriptor: FileDescriptor, options: RawProgressOptions = {}) {
const { disableWrite, filename, onProgress, ...requestOptions } = options;
const latestDescriptor = await this.client.descriptors.getLatestDescriptor(
descriptor
);
Expand Down Expand Up @@ -119,7 +119,8 @@ export default class Files extends Endpoint {
},
{
customHostname: fileUrl,
raw: true
raw: true,
onProgress
}
);

Expand Down
13 changes: 12 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,15 @@ export type ErrorMap = {
[mode: string]: Error
};

export type ProgressCallback = (
receivedBytes: number,
totalBytes: number
) => void;

export type ApiRequestOptions = {
customHostname?: string,
raw?: boolean
raw?: boolean,
onProgress?: ProgressCallback
};

export type RequestOptions = {
Expand All @@ -115,6 +121,11 @@ export type RawOptions = {
filename?: string
};

export type RawProgressOptions = {
...RawOptions,
onProgress?: ProgressCallback
};

export type AccessToken = ?string | ShareDescriptor;
export type AccessTokenOption =
| AccessToken // TODO: Deprecate?
Expand Down
5 changes: 3 additions & 2 deletions src/util/testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ export function mockPreviewAPI(
(nock("http://previewurl"): any)[method](url).reply(code, response);
}

export function mockAssetAPI(
export function mockObjectAPI(
url: string,
response: Object,
code: number = 200,
method: string = "get"
) {
(nock("http://objecturl"): any)[method](url).reply(code, response);
const base = (nock("http://objecturl"): any).replyContentLength();
base[method](url).reply(code, response);
}
6 changes: 3 additions & 3 deletions tests/endpoints/Assets.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import { mockAPI, mockAssetAPI, API_CLIENT } from "../../src/util/testing";
import { mockAPI, mockObjectAPI, API_CLIENT } from "../../src/util/testing";

describe("assets", () => {
describe("info", () => {
Expand Down Expand Up @@ -35,7 +35,7 @@ describe("assets", () => {
url: "https://objects.goabstract.com/foo"
});

mockAssetAPI("/foo", {
mockObjectAPI("/foo", {
id: "asset-id"
});

Expand All @@ -62,7 +62,7 @@ describe("assets", () => {
url: "https://objects.goabstract.com/foo"
});

mockAssetAPI("/foo", {
mockObjectAPI("/foo", {
id: "asset-id"
});

Expand Down
12 changes: 8 additions & 4 deletions tests/endpoints/Files.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import {
mockAssetAPI,
mockObjectAPI,
mockAPI,
mockCLI,
API_CLIENT,
Expand Down Expand Up @@ -132,7 +132,7 @@ describe("files", () => {
global.setTimeout = globalSetTimeout;
});

test("api - node", async () => {
test("api - node with progress", async () => {
mockAPI("/projects/project-id/branches/branch-id/files", {
files: [
{
Expand Down Expand Up @@ -163,7 +163,7 @@ describe("files", () => {
"post"
);

mockAssetAPI("/file", {
mockObjectAPI("/file", {
id: "file-id"
});

Expand All @@ -175,7 +175,11 @@ describe("files", () => {
sha: "sha"
},
{
disableWrite: true
disableWrite: true,
onProgress: (received, total) => {
expect(received).toBe(16);
expect(total).toBe(16);
}
}
);

Expand Down

0 comments on commit 393b893

Please sign in to comment.