Skip to content

Commit

Permalink
refactor(admin-ui): Move data product detail data fetching into service
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Jul 9, 2019
1 parent 601c766 commit 894ea7b
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 124 deletions.
3 changes: 2 additions & 1 deletion admin-ui/src/app/catalog/catalog.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ProductSearchInputComponent } from './components/product-search-input/p
import { ProductVariantsListComponent } from './components/product-variants-list/product-variants-list.component';
import { ProductVariantsTableComponent } from './components/product-variants-table/product-variants-table.component';
import { VariantPriceDetailComponent } from './components/variant-price-detail/variant-price-detail.component';
import { ProductDetailService } from './providers/product-detail.service';
import { CollectionResolver } from './providers/routing/collection-resolver';
import { FacetResolver } from './providers/routing/facet-resolver';
import { ProductResolver } from './providers/routing/product-resolver';
Expand Down Expand Up @@ -59,6 +60,6 @@ import { ProductResolver } from './providers/routing/product-resolver';
OptionValueInputComponent,
],
entryComponents: [AssetPickerDialogComponent, ApplyFacetDialogComponent, AssetPreviewComponent],
providers: [ProductResolver, FacetResolver, CollectionResolver],
providers: [ProductResolver, FacetResolver, CollectionResolver, ProductDetailService],
})
export class CatalogModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Location } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, combineLatest, forkJoin, merge, Observable, of } from 'rxjs';
import { distinctUntilChanged, map, mergeMap, shareReplay, skip, take, withLatestFrom } from 'rxjs/operators';
import { combineLatest, merge, Observable } from 'rxjs';
import { distinctUntilChanged, map, mergeMap, take, withLatestFrom } from 'rxjs/operators';
import { normalizeString } from 'shared/normalize-string';
import { CustomFieldConfig } from 'shared/shared-types';
import { notNullOrUndefined } from 'shared/shared-utils';
Expand All @@ -13,7 +13,6 @@ import { IGNORE_CAN_DEACTIVATE_GUARD } from 'src/app/shared/providers/routing/ca
import { BaseDetailComponent } from '../../../common/base-detail.component';
import {
CreateProductInput,
CreateProductVariantInput,
FacetWithValues,
LanguageCode,
ProductWithVariants,
Expand All @@ -27,9 +26,9 @@ import { createUpdatedTranslatable } from '../../../common/utilities/create-upda
import { flattenFacetValues } from '../../../common/utilities/flatten-facet-values';
import { _ } from '../../../core/providers/i18n/mark-for-extraction';
import { NotificationService } from '../../../core/providers/notification/notification.service';
import { DataService } from '../../../data/providers/data.service';
import { ServerConfigService } from '../../../data/server-config';
import { ModalService } from '../../../shared/providers/modal/modal.service';
import { ProductDetailService } from '../../providers/product-detail.service';
import { ApplyFacetDialogComponent } from '../apply-facet-dialog/apply-facet-dialog.component';
import { CreateProductVariantsConfig } from '../generate-product-variants/generate-product-variants.component';
import { VariantAssetChange } from '../product-variants-list/product-variants-list.component';
Expand Down Expand Up @@ -71,7 +70,7 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
assetChanges: SelectedAssets = {};
variantAssetChanges: { [variantId: string]: SelectedAssets } = {};
facetValues$: Observable<ProductWithVariants.FacetValues[]>;
facets$ = new BehaviorSubject<FacetWithValues.Fragment[]>([]);
facets$: Observable<FacetWithValues.Fragment[]>;
selectedVariantIds: string[] = [];
variantDisplayMode: 'card' | 'table' = 'card';
createVariantsConfig: CreateProductVariantsConfig = { groups: [], variants: [] };
Expand All @@ -80,7 +79,7 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
route: ActivatedRoute,
router: Router,
serverConfigService: ServerConfigService,
private dataService: DataService,
private productDetailService: ProductDetailService,
private formBuilder: FormBuilder,
private modalService: ModalService,
private notificationService: NotificationService,
Expand Down Expand Up @@ -109,16 +108,14 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
this.init();
this.product$ = this.entity$;
this.variants$ = this.product$.pipe(map(product => product.variants));
this.taxCategories$ = this.dataService.settings
.getTaxCategories()
.mapSingle(data => data.taxCategories)
.pipe(shareReplay(1));
this.taxCategories$ = this.productDetailService.getTaxCategories();
this.activeTab$ = this.route.paramMap.pipe(map(qpm => qpm.get('tab') as any));

// FacetValues are provided initially by the nested array of the
// Product entity, but once a fetch to get all Facets is made (as when
// opening the FacetValue selector modal), then these additional values
// are concatenated onto the initial array.
this.facets$ = this.productDetailService.getFacets();
const productFacetValues$ = this.product$.pipe(map(product => product.facetValues));
const allFacetValues$ = this.facets$.pipe(map(flattenFacetValues));
const productGroup = this.getProductFormGroup();
Expand Down Expand Up @@ -241,18 +238,7 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
}

private displayFacetValueModal(): Observable<string[] | undefined> {
let skipValue = 0;
if (this.facets$.value.length === 0) {
this.dataService.facet
.getFacets(9999999, 0)
.mapSingle(data => data.facets.items)
.subscribe(items => this.facets$.next(items));
skipValue = 1;
}

return this.facets$.pipe(
skip(skipValue),
take(1),
return this.productDetailService.getFacets().pipe(
mergeMap(facets =>
this.modalService.fromComponent(ApplyFacetDialogComponent, {
size: 'md',
Expand All @@ -277,86 +263,11 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
productGroup as FormGroup,
languageCode,
) as CreateProductInput;
const createProduct$ = this.dataService.product.createProduct(newProduct);

const createOptionGroups$ = this.createVariantsConfig.groups.length
? forkJoin(
this.createVariantsConfig.groups.map(c => {
return this.dataService.product.createProductOptionGroups({
code: normalizeString(c.name, '-'),
translations: [{ languageCode, name: c.name }],
options: c.values.map(v => ({
code: normalizeString(v, '-'),
translations: [{ languageCode, name: v }],
})),
});
}),
)
: of([]);

return forkJoin(createProduct$, createOptionGroups$).pipe(
mergeMap(([{ createProduct }, createOptionGroups]) => {
const optionGroups = createOptionGroups.map(g => g.createProductOptionGroup);
const addOptionsToProduct$ = optionGroups.length
? forkJoin(
optionGroups.map(optionGroup => {
return this.dataService.product.addOptionGroupToProduct({
productId: createProduct.id,
optionGroupId: optionGroup.id,
});
}),
)
: of([]);
return addOptionsToProduct$.pipe(
map(() => {
return { createProduct, optionGroups, languageCode };
}),
);
}),
);
}),
mergeMap(({ createProduct, optionGroups, languageCode }) => {
const variants: CreateProductVariantInput[] = this.createVariantsConfig.variants.map(
v => {
const optionIds = optionGroups.length
? v.optionValues.map((optionName, index) => {
const option = optionGroups[index].options.find(
o => o.name === optionName,
);
if (!option) {
throw new Error(
`Could not find a matching ProductOption "${optionName}" when creating variant`,
);
}
return option.id;
})
: [];
const name = optionGroups.length
? `${createProduct.name} ${v.optionValues.join(' ')}`
: createProduct.name;
return {
productId: createProduct.id,
price: v.price,
sku: v.sku,
stockOnHand: v.stock,
translations: [
{
languageCode,
name,
},
],
optionIds,
};
},
return this.productDetailService.createProduct(
newProduct,
this.createVariantsConfig,
languageCode,
);
return this.dataService.product
.createProductVariants(variants)
.pipe(
map(({ createProductVariants }) => ({
createProductVariants,
productId: createProduct.id,
})),
);
}),
)
.subscribe(
Expand Down Expand Up @@ -385,31 +296,26 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
take(1),
mergeMap(([product, languageCode]) => {
const productGroup = this.getProductFormGroup();
const updateOperations: Array<
Observable<UpdateProductMutation | UpdateProductVariantsMutation>
> = [];
let productInput: UpdateProductInput | undefined;
let variantsInput: UpdateProductVariantInput[] | undefined;

if (productGroup.dirty || this.assetsChanged()) {
const newProduct = this.getUpdatedProduct(
productInput = this.getUpdatedProduct(
product,
productGroup as FormGroup,
languageCode,
) as UpdateProductInput;
if (newProduct) {
updateOperations.push(this.dataService.product.updateProduct(newProduct));
}
}
const variantsArray = this.detailForm.get('variants');
if ((variantsArray && variantsArray.dirty) || this.variantAssetsChanged()) {
const newVariants = this.getUpdatedProductVariants(
variantsInput = this.getUpdatedProductVariants(
product,
variantsArray as FormArray,
languageCode,
);
updateOperations.push(this.dataService.product.updateProductVariants(newVariants));
}

return forkJoin(updateOperations);
return this.productDetailService.updateProduct(languageCode, productInput, variantsInput);
}),
)
.subscribe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
}

ngOnChanges(changes: SimpleChanges) {
if ('facets' in changes) {
if ('facets' in changes && !!changes['facets'].currentValue) {
this.facetValues = flattenFacetValues(this.facets);
}
}
Expand Down
144 changes: 144 additions & 0 deletions admin-ui/src/app/catalog/providers/product-detail.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, forkJoin, Observable, of } from 'rxjs';
import { map, mergeMap, shareReplay, skip } from 'rxjs/operators';
import { normalizeString } from 'shared/normalize-string';

import {
CreateProductInput,
CreateProductVariantInput,
FacetWithValues,
LanguageCode,
UpdateProductInput,
UpdateProductMutation,
UpdateProductVariantInput,
UpdateProductVariantsMutation,
} from '../../common/generated-types';
import { DataService } from '../../data/providers/data.service';
import { CreateProductVariantsConfig } from '../components/generate-product-variants/generate-product-variants.component';

/**
* Handles the logic for making the API calls to perform CRUD operations on a Product and its related
* entities. This logic was extracted out of the component because it became too large and hard to follow.
*/
@Injectable()
export class ProductDetailService {
private facetsSubject = new BehaviorSubject<FacetWithValues.Fragment[]>([]);

constructor(private dataService: DataService) {}

getFacets(): Observable<FacetWithValues.Fragment[]> {
let skipValue = 0;
if (this.facetsSubject.value.length === 0) {
this.dataService.facet
.getFacets(9999999, 0)
.mapSingle(data => data.facets.items)
.subscribe(items => this.facetsSubject.next(items));
skipValue = 1;
}

return this.facetsSubject.pipe(skip(skipValue));
}

getTaxCategories() {
return this.dataService.settings
.getTaxCategories()
.mapSingle(data => data.taxCategories)
.pipe(shareReplay(1));
}

createProduct(
input: CreateProductInput,
createVariantsConfig: CreateProductVariantsConfig,
languageCode: LanguageCode,
) {
const createProduct$ = this.dataService.product.createProduct(input);

const createOptionGroups$ = createVariantsConfig.groups.length
? forkJoin(
createVariantsConfig.groups.map(c => {
return this.dataService.product.createProductOptionGroups({
code: normalizeString(c.name, '-'),
translations: [{ languageCode, name: c.name }],
options: c.values.map(v => ({
code: normalizeString(v, '-'),
translations: [{ languageCode, name: v }],
})),
});
}),
)
: of([]);

return forkJoin(createProduct$, createOptionGroups$).pipe(
mergeMap(([{ createProduct }, createOptionGroups]) => {
const optionGroups = createOptionGroups.map(g => g.createProductOptionGroup);
const addOptionsToProduct$ = optionGroups.length
? forkJoin(
optionGroups.map(optionGroup => {
return this.dataService.product.addOptionGroupToProduct({
productId: createProduct.id,
optionGroupId: optionGroup.id,
});
}),
)
: of([]);
return addOptionsToProduct$.pipe(
map(() => {
return { createProduct, optionGroups };
}),
);
}),
mergeMap(({ createProduct, optionGroups }) => {
const variants: CreateProductVariantInput[] = createVariantsConfig.variants.map(v => {
const optionIds = optionGroups.length
? v.optionValues.map((optionName, index) => {
const option = optionGroups[index].options.find(o => o.name === optionName);
if (!option) {
throw new Error(
`Could not find a matching ProductOption "${optionName}" when creating variant`,
);
}
return option.id;
})
: [];
const name = optionGroups.length
? `${createProduct.name} ${v.optionValues.join(' ')}`
: createProduct.name;
return {
productId: createProduct.id,
price: v.price,
sku: v.sku,
stockOnHand: v.stock,
translations: [
{
languageCode,
name,
},
],
optionIds,
};
});
return this.dataService.product.createProductVariants(variants).pipe(
map(({ createProductVariants }) => ({
createProductVariants,
productId: createProduct.id,
})),
);
}),
);
}

updateProduct(
languageCode: LanguageCode,
productInput?: UpdateProductInput,
variantInput?: UpdateProductVariantInput[],
) {
const updateOperations: Array<Observable<UpdateProductMutation | UpdateProductVariantsMutation>> = [];
if (productInput) {
updateOperations.push(this.dataService.product.updateProduct(productInput));
}
if (variantInput) {
updateOperations.push(this.dataService.product.updateProductVariants(variantInput));
}
return forkJoin(updateOperations);
}
}
Loading

0 comments on commit 894ea7b

Please sign in to comment.