Skip to content

Commit

Permalink
Fetch products on login; add getProductAbbreviation function (#406)
Browse files Browse the repository at this point in the history
* Start of expanded ProductAreasService

* Remove unused functions and classes

* Update tests

* Test abbreviation logic change

* Test cleanup

* Fix failing tests

* Add fetch call to ProductSelect

* Fetch product areas in the background
  • Loading branch information
jeffdaley authored Nov 8, 2023
1 parent e3116b9 commit fdbdc08
Show file tree
Hide file tree
Showing 19 changed files with 295 additions and 192 deletions.
140 changes: 61 additions & 79 deletions web/app/components/inputs/product-select.hbs
Original file line number Diff line number Diff line change
@@ -1,83 +1,65 @@
{{! https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/ }}
<div data-test-product-select>
{{#if this.products}}
{{#if @formatIsBadge}}
<Inputs::BadgeDropdownList
@items={{this.products}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@secondaryFilterAttribute="abbreviation"
@icon={{this.icon}}
class="product-select-dropdown-list w-80"
...attributes
>
<:item as |dd|>
<dd.Action data-test-product-select-badge-dropdown-item>
<Inputs::ProductSelect::Item
@product={{dd.value}}
@isSelected={{dd.isSelected}}
/>
</dd.Action>
</:item>
</Inputs::BadgeDropdownList>
{{else}}
<X::DropdownList
@items={{this.products}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@offset={{@offset}}
@matchAnchorWidth={{@matchAnchorWidth}}
@secondaryFilterAttribute="abbreviation"
class="product-select-dropdown-list"
...attributes
>
<:anchor as |dd|>
<dd.ToggleSelect class="gap-2" id="product-select">
<Inputs::ProductSelect::Item
@product={{dd.selected}}
@abbreviation={{this.selectedProductAbbreviation}}
/>
</dd.ToggleSelect>
</:anchor>
<:item as |dd|>
<dd.Action data-test-product-select-item-button class="pr-5">
<Inputs::ProductSelect::Item
@product={{dd.value}}
@isSelected={{dd.isSelected}}
@abbreviation={{dd.attrs.abbreviation}}
/>
</dd.Action>
</:item>
</X::DropdownList>
{{/if}}
{{else if this.fetchProductAreas.isRunning}}
<div class="flex h-6 items-center" I>
<FlightIcon data-test-product-select-spinner @name="loading" />
</div>
{{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"
/>
<div
data-test-product-select
{{! Fetch the most up-to-date product areas in the background }}
{{did-insert (perform this.productAreas.fetch)}}
>
{{#if @formatIsBadge}}
<Inputs::BadgeDropdownList
@items={{this.products}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@secondaryFilterAttribute="abbreviation"
@icon={{this.icon}}
class="product-select-dropdown-list w-80"
...attributes
>
<:item as |dd|>
<dd.Action data-test-product-select-badge-dropdown-item>
<Inputs::ProductSelect::Item
@product={{dd.value}}
@isSelected={{dd.isSelected}}
@abbreviation={{dd.attrs.abbreviation}}
/>
</dd.Action>
</:item>
</Inputs::BadgeDropdownList>
{{else}}
<div
class="absolute top-0 left-0"
{{did-insert (perform this.fetchProductAreas)}}
></div>
<X::DropdownList
@items={{this.products}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@offset={{@offset}}
@matchAnchorWidth={{@matchAnchorWidth}}
@secondaryFilterAttribute="abbreviation"
class="product-select-dropdown-list"
...attributes
>
<:anchor as |dd|>
<dd.ToggleSelect class="gap-2" id="product-select">
<Inputs::ProductSelect::Item
@product={{dd.selected}}
@abbreviation={{this.selectedProductAbbreviation}}
/>
</dd.ToggleSelect>
</:anchor>
<:item as |dd|>
<dd.Action data-test-product-select-item-button class="pr-5">
<Inputs::ProductSelect::Item
@product={{dd.value}}
@isSelected={{dd.isSelected}}
@abbreviation={{dd.attrs.abbreviation}}
/>
</dd.Action>
</:item>
</X::DropdownList>
{{/if}}
</div>
18 changes: 1 addition & 17 deletions web/app/components/inputs/product-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { inject as service } from "@ember/service";
import { OffsetOptions, Placement } from "@floating-ui/dom";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { task } from "ember-concurrency";
import FetchService from "hermes/services/fetch";
import ProductAreasService, {
ProductArea,
Expand All @@ -31,7 +30,6 @@ export default class InputsProductSelectComponent extends Component<InputsProduc
@service declare productAreas: ProductAreasService;

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

get products() {
return this.productAreas.index;
Expand All @@ -46,27 +44,13 @@ export default class InputsProductSelectComponent extends Component<InputsProduc
}

get selectedProductAbbreviation(): string | undefined {
if (!this.selected) {
return;
}
const selectedProduct = this.products?.[this.selected];
assert("selected product must exist", selectedProduct);
return selectedProduct.abbreviation;
return this.productAreas.getAbbreviation(this.selected);
}

@action onChange(newValue: any, attributes: ProductArea) {
this.selected = newValue;
this.args.onChange(newValue, attributes);
}

protected fetchProductAreas = task(async () => {
try {
await this.productAreas.fetch.perform();
this.errorIsShown = false;
} catch {
this.errorIsShown = true;
}
});
}

declare module "@glint/environment-ember-loose/registry" {
Expand Down
6 changes: 3 additions & 3 deletions web/app/components/inputs/product-select/item.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div
data-test-product-select-item
class="product-select-item relative flex w-full gap-2"
class="relative flex w-full gap-2"
...attributes
>
<FlightIcon
Expand All @@ -19,8 +19,8 @@
<EmptyStateText data-test-empty-state @value="Select a product/area" />
{{/if}}

{{#if @abbreviation}}
<span class="product-select-toggle-abbreviation">
{{#if this.abbreviationIsShown}}
<span data-test-product-select-item-abbreviation>
{{@abbreviation}}
</span>
{{/if}}
Expand Down
10 changes: 9 additions & 1 deletion web/app/components/inputs/product-select/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ interface InputsProductSelectItemComponentSignature {
};
}

export default class InputsProductSelectItemComponent extends Component<InputsProductSelectItemComponentSignature> {}
export default class InputsProductSelectItemComponent extends Component<InputsProductSelectItemComponentSignature> {
protected get abbreviationIsShown(): boolean {
const { abbreviation, product } = this.args;
if (abbreviation && abbreviation !== product) {
return true;
}
return false;
}
}

declare module "@glint/environment-ember-loose/registry" {
export default interface Registry {
Expand Down
7 changes: 3 additions & 4 deletions web/app/components/settings/subscription-list-item.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{{! @glint-nocheck: not typesafe yet }}
<li class="subscription-list-item">
<button
class="text-body-300 text-color-foreground-primary flex items-center space-x-6 hover:cursor-pointer bg-transparent hover:bg-color-surface-action border-0 w-full h-full justify-between py-3 px-3.5 transition-colors"
class="flex h-full w-full items-center justify-between space-x-6 border-0 bg-transparent py-3 px-3.5 text-body-300 text-color-foreground-primary transition-colors hover:cursor-pointer hover:bg-color-surface-action"
type="button"
{{on "click" this.toggleChecked}}
>
<div class="flex space-x-3 items-center">
<div class="flex items-center space-x-3">
{{#let (get-product-id @productArea) as |productID|}}
<Hds::IconTile
@size="small"
Expand All @@ -17,7 +16,7 @@
{{@productArea}}
</div>
</div>
<div class="shrink-0 flex items-center justify-end relative">
<div class="relative flex shrink-0 items-center justify-end">
<Hds::Form::Toggle::Base
tabindex="-1"
name="toggle-subscription"
Expand Down
14 changes: 10 additions & 4 deletions web/app/components/settings/subscription-list-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export default class SettingsSubscriptionListItemComponent extends Component<Set
protected get isChecked(): boolean {
assert(
"isChecked expects a subscriptions list",
this.authenticatedUser.subscriptions
this.authenticatedUser.subscriptions,
);
return this.authenticatedUser.subscriptions.some(
(subscription) => subscription.productArea === this.args.productArea
(subscription) => subscription.productArea === this.args.productArea,
);
}

Expand All @@ -41,11 +41,11 @@ export default class SettingsSubscriptionListItemComponent extends Component<Set
@action protected toggleChecked(): void {
if (this.isChecked) {
void this.authenticatedUser.removeSubscription.perform(
this.args.productArea
this.args.productArea,
);
} else {
void this.authenticatedUser.addSubscription.perform(
this.args.productArea
this.args.productArea,
);
}
void this.temporarilyShowConfirmation.perform();
Expand All @@ -67,3 +67,9 @@ export default class SettingsSubscriptionListItemComponent extends Component<Set
this.confirmationIsClosing = false;
});
}

declare module "@glint/environment-ember-loose/registry" {
export default interface Registry {
"Settings::SubscriptionListItem": typeof SettingsSubscriptionListItemComponent;
}
}
3 changes: 2 additions & 1 deletion web/app/components/settings/subscription-list.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{{! @glint-nocheck: not typesafe yet }}
<Hds::Form::TextInput::Field
data-test-subscription-list-filter-input
@type="search"
Expand All @@ -9,6 +8,8 @@
/>
<ol
data-test-subscription-list
{{! Fetch the most up-to-date product areas in the background }}
{{did-insert (perform this.productAreas.fetch)}}
class="mt-5 w-full divide-y divide-color-border-primary"
>
{{#each this.shownItems as |listItem|}}
Expand Down
14 changes: 12 additions & 2 deletions web/app/components/settings/subscription-list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Component from "@glimmer/component";
import { restartableTask, task } from "ember-concurrency";
import { restartableTask } from "ember-concurrency";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import ProductAreasService from "hermes/services/product-areas";

interface SettingsSubscriptionListComponentSignature {
Args: {
Expand All @@ -9,6 +11,8 @@ interface SettingsSubscriptionListComponentSignature {
}

export default class SettingsSubscriptionListComponent extends Component<SettingsSubscriptionListComponentSignature> {
@service declare productAreas: ProductAreasService;

/**
* The list of product areas to show. Updated by the `onInput` task.
*/
Expand All @@ -22,10 +26,16 @@ export default class SettingsSubscriptionListComponent extends Component<Setting
let input = event.target.value;
if (input.length > 0) {
this.shownItems = this.args.allProductAreas.filter((item) =>
item.toLowerCase().includes(input.toLowerCase())
item.toLowerCase().includes(input.toLowerCase()),
);
} else {
this.shownItems = this.args.allProductAreas;
}
});
}

declare module "@glint/environment-ember-loose/registry" {
export default interface Registry {
"Settings::SubscriptionList": typeof SettingsSubscriptionListComponent;
}
}
15 changes: 14 additions & 1 deletion web/app/routes/authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";
import AuthenticatedUserService from "hermes/services/authenticated-user";
import ConfigService from "hermes/services/config";
import ProductAreasService from "hermes/services/product-areas";
import SessionService from "hermes/services/session";

export default class AuthenticatedRoute extends Route {
@service("config") declare configSvc: ConfigService;
@service declare session: SessionService;
@service declare authenticatedUser: AuthenticatedUserService;
@service declare productAreas: ProductAreasService;

beforeModel(transition: any) {
/**
Expand All @@ -27,7 +29,18 @@ export default class AuthenticatedRoute extends Route {
* application error method which invalidates the session
* and redirects to the auth screen.
*/
await this.authenticatedUser.loadInfo.perform();
const loadInfoPromise = this.authenticatedUser.loadInfo.perform();

/**
* Fetch the product areas for the ProductAvatar and
* ProductSelect components.
*/
const loadProductAreasPromise = this.productAreas.fetch.perform();

/**
* Wait for both promises to resolve.
*/
await Promise.all([loadInfoPromise, loadProductAreasPromise]);

/**
* Kick off the task to poll for expired auth.
Expand Down
6 changes: 1 addition & 5 deletions web/app/routes/authenticated/new/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ export default class AuthenticatedNewDocRoute extends Route {
},
};

async model(params: AuthenticatedNewDocRouteParams) {
if (!this.productAreas.index) {
await this.productAreas.fetch.perform();
}

model(params: AuthenticatedNewDocRouteParams) {
return params.docType;
}
}
Loading

0 comments on commit fdbdc08

Please sign in to comment.