Skip to content

Commit

Permalink
feat(services): add document service for upload and replace
Browse files Browse the repository at this point in the history
This moves the file upload logic to a service but leaves the handling
in the components for more control.
  • Loading branch information
fkm-adfinis committed Dec 1, 2020
1 parent dd8bc22 commit a1d0a2f
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 94 deletions.
19 changes: 2 additions & 17 deletions addon/components/document-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DocumentCard from "./document-card";
export default class DocumentDetailsComponent extends DocumentCard {
@service router;
@service store;
@service document;

@tracked editTitle = false;
@tracked validTitle = true;
Expand Down Expand Up @@ -42,23 +43,7 @@ export default class DocumentDetailsComponent extends DocumentCard {
@dropTask *uploadReplacement(event) {
try {
const [file] = event.target.files;

const fileModel = this.store.createRecord("file", {
name: file.name,
type: "original",
document: this.args.document,
});

yield fileModel.save();

const response = yield fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
});

if (!response.ok) {
throw new Error("The request returned an error status code");
}
yield this.document.replace(this.args.document, file);
} catch (error) {
this.notification.danger(
this.intl.t("alexandria.errors.replace-document")
Expand Down
23 changes: 8 additions & 15 deletions addon/components/document-grid.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,16 @@
</div>

<div
class="
uk-position-relative
uk-height-1-1
{{if this.isDragOver "document-grid--dragover"}}
"
class="uk-position-relative uk-height-1-1 {{
if this.isDragOver "document-grid--dragover"
}}"
{{on "dragenter" this.onDragEnter}}
{{on "dragleave" this.onDragLeave}}
{{on "dragover" this.onDragOver}}
{{on "drop" this.onDrop}}
>
<div
class="
uk-position-cover
uk-padding-small
uk-height-1-1 uk-overflow-auto
"
class="uk-position-cover uk-padding-small uk-height-1-1 uk-overflow-auto"
...attributes
>
<div
Expand All @@ -44,7 +38,9 @@
{{#if this.fetchDocuments.isRunning}}
{{#each (range 0 10) as |index|}}
<div>
<DocumentCard::Skeleton @animationDelay={{concat index "00ms"}} />
<DocumentCard::Skeleton
@animationDelay={{concat index "00ms"}}
/>
</div>
{{/each}}
{{else}}
Expand Down Expand Up @@ -77,10 +73,7 @@
</div>
{{#if this.isDragOver}}
<button
class="uk-width-1
uk-padding-remove
uk-position-bottom
uk-button uk-button-primary"
class="uk-width-1 uk-padding-remove uk-position-bottom uk-button uk-button-primary"
type="button"
>
{{t "alexandria.document-grid.drop-to-upload"}}
Expand Down
32 changes: 2 additions & 30 deletions addon/components/document-grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default class DocumentGridComponent extends Component {
@service config;
@service store;
@service intl;
@service document;

@tracked isDragOver = false;

Expand Down Expand Up @@ -80,36 +81,7 @@ export default class DocumentGridComponent extends Component {
const { files } = event.dataTransfer;

try {
const category =
(await this.store.peekRecord("category", this.args.filters.category)) ||
this.store.findRecord("category", this.args.filters.category);

await Promise.all(
Array.from(files).map(async (file) => {
const documentModel = this.store.createRecord("document", {
category,
meta: this.config.defaultModelMeta.document,
});
documentModel.title = file.name;
await documentModel.save();

const fileModel = this.store.createRecord("file", {
name: file.name,
type: "original",
document: documentModel,
});
await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
});

if (!response.ok) {
throw new Error("The request returned an error status code");
}
})
);
await this.document.upload(this.args.filters.category, files);

this.notification.success(
this.intl.t("alexandria.success.upload-document", {
Expand Down
35 changes: 3 additions & 32 deletions addon/components/document-upload-button.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,25 @@
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { task, lastValue } from "ember-concurrency-decorators";
import fetch from "fetch";

export default class DocumentUploadButtonComponent extends Component {
@service notification;
@service intl;
@service store;
@service config;
@service document;

@lastValue("fetchCategories") categories;

@task *upload(category, { target: { files = [] } = {} }) {
try {
if (!category.id) {
category = yield this.store.peekRecord("category", category) ||
this.store.findRecord("category", category);
}
yield this.document.upload(category, files);

yield Promise.all(
Array.from(files).map(async (file) => {
const documentModel = this.store.createRecord("document", {
category,
meta: this.config.defaultModelMeta.document,
});
documentModel.title = file.name;
await documentModel.save();

const fileModel = this.store.createRecord("file", {
name: file.name,
type: "original",
document: documentModel,
});
await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "put",
body: file,
});

if (!response.ok) {
throw new Error("The request returned an error status code");
}
})
);
this.notification.success(
this.intl.t("alexandria.success.upload-document", {
count: files.length,
})
);

this.args.afterUpload();
} catch (error) {
this.notification.danger(
Expand Down
74 changes: 74 additions & 0 deletions addon/services/document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Service, { inject as service } from "@ember/service";
import fetch from "fetch";

export default class DocumentService extends Service {
@service store;
@service config;

/**
* Uploads one or multipl files and creates the necessary document and
* files entries on the API.
*
* @param {Object|String|Number} category Either an ID or category instance.
* @param {Array<File>} files The file(s) from input[type=file].
*/
async upload(category, files) {
if (!category.id) {
category =
(await this.store.peekRecord("category", category)) ||
this.store.findRecord("category", category);
}

await Promise.all(
Array.from(files).map(async (file) => {
const documentModel = this.store.createRecord("document", {
category,
meta: this.config.defaultModelMeta.document,
});
documentModel.title = file.name;
await documentModel.save();

const fileModel = this.store.createRecord("file", {
name: file.name,
type: "original",
document: documentModel,
});
await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
});

if (!response.ok) {
throw new Error(response.statusText, response.status);
}
})
);
}

/**
* Uploads a new version of a file and creates the necessary API entry.
*
* @param {Object} document A document instance.
* @param {File} file The file from input[type=file].
*/
async replace(document, file) {
const fileModel = this.store.createRecord("file", {
name: file.name,
type: "original",
document,
});

await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
});

if (!response.ok) {
throw new Error(response.statusText, response.status);
}
}
}
1 change: 1 addition & 0 deletions app/services/document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "ember-alexandria/services/document";
61 changes: 61 additions & 0 deletions tests/unit/services/document-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { setupMirage } from "ember-cli-mirage/test-support";
import { setupTest } from "ember-qunit";
import { module, test } from "qunit";

module("Unit | Service | document", function (hooks) {
setupTest(hooks);
setupMirage(hooks);

test("it exists", function (assert) {
const service = this.owner.lookup("service:document");
assert.ok(service);
});

test("it uploads documents", async function (assert) {
const requests = this.server.pretender.handledRequests;

const service = this.owner.lookup("service:document");

const category = this.server.create("category");
const files = [
new File(["1"], "test1.docx"),
new File(["2"], "test2.docx"),
new File(["3"], "test3.docx"),
];

await service.upload(category, files);

// Each file generates three requests.
assert.equal(requests.length, files.length * 3);

// Files will be uploaded in parallel. So, we cannot know the order.
const documentRequests = requests.filter((request) =>
request.url.endsWith("documents")
);
const fileRequests = requests.filter((request) =>
request.url.endsWith("files")
);
const uploadRequests = requests.filter((request) =>
request.url.endsWith("file-upload")
);

assert.equal(documentRequests.length, files.length);
assert.equal(fileRequests.length, files.length);
assert.equal(uploadRequests.length, files.length);
});

test("it replaces documents", async function (assert) {
const requests = this.server.pretender.handledRequests;

const service = this.owner.lookup("service:document");

const document = this.server.create("document");
const file = new File([""], "test.docx");

await service.replace(document, file);

assert.equal(requests.length, 2);
assert.ok(requests[0].url.endsWith("files"));
assert.ok(requests[1].url.endsWith("file-upload"));
});
});

0 comments on commit a1d0a2f

Please sign in to comment.