Skip to content

Commit

Permalink
Add ProductArea service (#316)
Browse files Browse the repository at this point in the history
* Move ProductArea index and fetch into service

* Update tests

* Update test selector
  • Loading branch information
jeffdaley authored Sep 1, 2023
1 parent 6d1bae1 commit 4e4a9b0
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 41 deletions.
2 changes: 1 addition & 1 deletion web/app/components/document/sidebar/related-resources.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</div>
{{else if this.loadingHasFailed}}
<div class="h-16">
<div class="related-resources-failed-to-load">
<div class="failed-to-load-text">
Failed to load
</div>
<Hds::Button
Expand Down
20 changes: 16 additions & 4 deletions web/app/components/inputs/product-select/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@icon={{this.icon}}
class="w-80 product-select-dropdown-list"
class="product-select-dropdown-list w-80"
...attributes
>
<:item as |dd|>
Expand All @@ -32,7 +32,7 @@
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
class="w-[300px] product-select-dropdown-list"
class="product-select-dropdown-list w-[300px]"
...attributes
>
<:anchor as |dd|>
Expand Down Expand Up @@ -68,12 +68,24 @@
</:item>
</X::DropdownList>
{{/if}}
{{else if this.fetchProducts.isRunning}}
{{else if this.fetchProductAreas.isRunning}}
<FlightIcon data-test-product-select-spinner @name="loading" />
{{else if this.errorIsShown}}
<div class="failed-to-load-text">
Failed to load
</div>
<Hds::Button
data-test-product-select-failed-to-load-button
@color="secondary"
@size="small"
{{on "click" (perform this.fetchProductAreas)}}
@text="Retry"
@icon="reload"
/>
{{else}}
<div
class="absolute top-0 left-0"
{{did-insert (perform this.fetchProducts)}}
{{did-insert (perform this.fetchProductAreas)}}
></div>
{{/if}}
</div>
31 changes: 13 additions & 18 deletions web/app/components/inputs/product-select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { task } from "ember-concurrency";
import FetchService from "hermes/services/fetch";
import ProductAreasService, {
ProductArea,
} from "hermes/services/product-areas";
import getProductId from "hermes/utils/get-product-id";

interface InputsProductSelectSignature {
Expand All @@ -20,21 +23,16 @@ interface InputsProductSelectSignature {
};
}

type ProductAreas = {
[key: string]: ProductArea;
};

export type ProductArea = {
abbreviation: string;
perDocDataType: unknown;
};

export default class InputsProductSelectComponent extends Component<InputsProductSelectSignature> {
@service("fetch") declare fetchSvc: FetchService;
@service declare productAreas: ProductAreasService;

@tracked selected = this.args.selected;
@tracked protected errorIsShown = false;

@tracked products: ProductAreas | undefined = undefined;
get products() {
return this.productAreas.index;
}

get icon(): string {
let icon = "folder";
Expand All @@ -58,15 +56,12 @@ export default class InputsProductSelectComponent extends Component<InputsProduc
this.args.onChange(newValue, attributes);
}

protected fetchProducts = task(async () => {
protected fetchProductAreas = task(async () => {
try {
let products = await this.fetchSvc
.fetch("/api/v1/products")
.then((resp) => resp?.json());
this.products = products;
} catch (err) {
console.error(err);
throw err;
await this.productAreas.fetch.perform();
this.errorIsShown = false;
} catch {
this.errorIsShown = true;
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/new/doc-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { HermesUser } from "hermes/types/document";
import FlashService from "ember-cli-flash/services/flash-messages";
import { assert } from "@ember/debug";
import cleanString from "hermes/utils/clean-string";
import { ProductArea } from "../inputs/product-select";
import { ProductArea } from "hermes/services/product-areas";

interface DocFormErrors {
title: string | null;
Expand Down
27 changes: 27 additions & 0 deletions web/app/services/product-areas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Service, { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import RouterService from "@ember/routing/router-service";
import { task, timeout } from "ember-concurrency";
import FetchService from "./fetch";

export type ProductArea = {
abbreviation: string;
};

export default class ProductAreasService extends Service {
@service("fetch") declare fetchSvc: FetchService;

@tracked index: Record<string, ProductArea> | null = null;

fetch = task(async () => {
try {
this.index = await this.fetchSvc
.fetch("/api/v1/products")
.then((resp) => resp?.json());
} catch (err) {
this.index = null;
throw err;
}
});
}
4 changes: 0 additions & 4 deletions web/app/styles/components/document/related-resources.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
}
}

.related-resources-failed-to-load {
@apply mb-2 text-display-300 font-semibold text-color-foreground-faint opacity-50;
}

.related-resources-modal-container {
@apply relative w-full px-3;
}
Expand Down
4 changes: 4 additions & 0 deletions web/app/styles/typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@
}
}
}

.failed-to-load-text {
@apply mb-2 text-display-300 font-semibold text-color-foreground-faint opacity-50;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const HERMES_DOCUMENT_SELECTOR = ".hermes-document";
const EXTERNAL_RESOURCE_SELECTOR = ".external-resource";
const BADGE_SELECTOR = "[data-test-sidebar-section-header-badge]";
const HEADER_SELECTOR = ".sidebar-section-header";
const ERROR_MESSAGE_SELECTOR = ".related-resources-failed-to-load";
const ERROR_MESSAGE_SELECTOR = ".failed-to-load-text";
const ERROR_BUTTON_SELECTOR = "[data-test-related-resources-error-button]";
const OVERFLOW_BUTTON_SELECTOR = ".related-resource-overflow-button";
const EDIT_BUTTON_SELECTOR = "[data-test-overflow-menu-action='edit']";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { click, render } from "@ember/test-helpers";
import { setupMirage } from "ember-cli-mirage/test-support";
import { MirageTestContext } from "ember-cli-mirage/test-support";
import { Placement } from "@floating-ui/dom";
import { Response } from "miragejs";

const DEFAULT_DROPDOWN_SELECTOR = ".product-select-default-toggle";
const LIST_ITEM_SELECTOR = "[data-test-product-select-item]";
Expand Down Expand Up @@ -37,8 +38,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {

this.set("formatIsBadge", true);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
Expand All @@ -64,8 +64,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {
test("it can render the toggle with a product abbreviation", async function (this: InputsProductSelectContext, assert) {
this.set("selected", this.server.schema.products.first().name);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
Expand All @@ -78,8 +77,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {
test("it shows an empty state when nothing is selected (default toggle)", async function (this: InputsProductSelectContext, assert) {
this.set("selected", undefined);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
Expand All @@ -91,8 +89,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {
});

test("it displays the products in a dropdown list with abbreviations", async function (this: InputsProductSelectContext, assert) {
await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
Expand All @@ -110,8 +107,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {
test("it fetches the products if they aren't already loaded", async function (this: InputsProductSelectContext, assert) {
this.server.db.emptyData();

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@onChange={{this.onChange}}
/>
Expand All @@ -131,8 +127,7 @@ module("Integration | Component | inputs/product-select", function (hooks) {
count++;
});

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
Expand All @@ -144,4 +139,22 @@ module("Integration | Component | inputs/product-select", function (hooks) {

assert.equal(count, 1, "the action was called once");
});

test("it shows an error when the index fails to fetch", async function (this: InputsProductSelectContext, assert) {
this.server.get("/products", () => {
return new Response(500, {});
});

await render<InputsProductSelectContext>(hbs`
<Inputs::ProductSelect
@selected={{this.selected}}
@onChange={{this.onChange}}
/>
`);

assert.dom(".failed-to-load-text").hasText("Failed to load");
assert
.dom("[data-test-product-select-failed-to-load-button]")
.hasText("Retry");
});
});
37 changes: 37 additions & 0 deletions web/tests/unit/services/product-areas-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { module, test, todo } from "qunit";
import { setupTest } from "ember-qunit";
import ProductAreasService from "hermes/services/product-areas";
import { MirageTestContext, setupMirage } from "ember-cli-mirage/test-support";
import { authenticateSession } from "ember-simple-auth/test-support";

module("Unit | Service | product-areas", function (hooks) {
setupTest(hooks);
setupMirage(hooks);

hooks.beforeEach(function () {
authenticateSession({});
});

test("can set or close an active modal", async function (this: MirageTestContext, assert) {
const productAreas = this.owner.lookup(
"service:product-areas"
) as ProductAreasService;

this.server.create("product", {
name: "Labs",
abbreviation: "LABS",
});

const expectedResponse = {
Labs: {
abbreviation: "LABS",
},
};

assert.equal(productAreas.index, null);

await productAreas.fetch.perform();

assert.deepEqual(productAreas.index, expectedResponse);
});
});

0 comments on commit 4e4a9b0

Please sign in to comment.