Skip to content

Commit

Permalink
feat(admin-ui): Auto update ProductVariant name with ProductOption name
Browse files Browse the repository at this point in the history
Relates to #600
  • Loading branch information
michaelbromley committed Jan 11, 2021
1 parent b1b363d commit 0e98cb5
Show file tree
Hide file tree
Showing 15 changed files with 118 additions and 50 deletions.
28 changes: 14 additions & 14 deletions packages/admin-ui/i18n-coverage.json
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
{
"generatedOn": "2021-01-11T09:10:41.279Z",
"lastCommit": "12c46425e453d3e160a27aed41f14bd23120c9f2",
"generatedOn": "2021-01-11T10:15:44.646Z",
"lastCommit": "b1b363d91d14b3484a30d889db74491e8813c5f0",
"translationStatus": {
"cs": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 687,
"percentage": 92
"percentage": 91
},
"de": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 596,
"percentage": 79
},
"en": {
"tokenCount": 750,
"translatedCount": 749,
"tokenCount": 751,
"translatedCount": 750,
"percentage": 100
},
"es": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 458,
"percentage": 61
},
"fr": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 692,
"percentage": 92
},
"pl": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 551,
"percentage": 73
},
"pt_BR": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 642,
"percentage": 86
"percentage": 85
},
"zh_Hans": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 533,
"percentage": 71
},
"zh_Hant": {
"tokenCount": 750,
"tokenCount": 751,
"translatedCount": 533,
"percentage": 71
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { normalizeString } from '@vendure/common/lib/normalize-string';
import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
import { unique } from '@vendure/common/lib/unique';
import { combineLatest, EMPTY, merge, Observable } from 'rxjs';
import { combineLatest, EMPTY, merge, Observable, of } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
Expand Down Expand Up @@ -347,19 +347,26 @@ export class ProductDetailComponent
});
}

updateProductOption(input: UpdateProductOptionInput) {
this.productDetailService.updateProductOption(input).subscribe(
() => {
this.notificationService.success(_('common.notify-update-success'), {
entity: 'ProductOption',
});
},
err => {
this.notificationService.error(_('common.notify-update-error'), {
entity: 'ProductOption',
});
},
);
updateProductOption(input: UpdateProductOptionInput & { autoUpdate: boolean }) {
combineLatest(this.product$, this.languageCode$)
.pipe(
take(1),
mergeMap(([product, languageCode]) =>
this.productDetailService.updateProductOption(input, product, languageCode),
),
)
.subscribe(
() => {
this.notificationService.success(_('common.notify-update-success'), {
entity: 'ProductOption',
});
},
err => {
this.notificationService.error(_('common.notify-update-error'), {
entity: 'ProductOption',
});
},
);
}

removeProductFacetValue(facetValueId: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
@Output() assetChange = new EventEmitter<VariantAssetChange>();
@Output() selectionChange = new EventEmitter<string[]>();
@Output() selectFacetValueClick = new EventEmitter<string[]>();
@Output() updateProductOption = new EventEmitter<UpdateProductOptionInput>();
@Output() updateProductOption = new EventEmitter<UpdateProductOptionInput & { autoUpdate: boolean }>();
selectedVariantIds: string[] = [];
pagination: PaginationInstance = {
currentPage: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<vdr-form-field [label]="'common.code' | translate" for="code">
<input id="code" type="text" #codeInput="ngModel" required [(ngModel)]="code" pattern="[a-z0-9_-]+" />
</vdr-form-field>
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox [(ngModel)]="updateVariantName" />
<label>{{ 'catalog.auto-update-product-variant-name' | translate }}</label>
</clr-checkbox-wrapper>
<section *ngIf="customFields.length">
<label>{{ 'common.custom-fields' | translate }}</label>
<ng-container *ngFor="let customField of customFields">
Expand All @@ -30,7 +34,11 @@
<button
type="submit"
(click)="update()"
[disabled]="nameInput.invalid || codeInput.invalid || (nameInput.pristine && codeInput.pristine && customFieldsForm.pristine)"
[disabled]="
nameInput.invalid ||
codeInput.invalid ||
(nameInput.pristine && codeInput.pristine && customFieldsForm.pristine)
"
class="btn btn-primary"
>
{{ 'catalog.update-product-option' | translate }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import { normalizeString } from '@vendure/common/lib/normalize-string';
styleUrls: ['./update-product-option-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UpdateProductOptionDialogComponent implements Dialog<UpdateProductOptionInput>, OnInit {
resolveWith: (result?: UpdateProductOptionInput) => void;
export class UpdateProductOptionDialogComponent
implements Dialog<UpdateProductOptionInput & { autoUpdate: boolean }>, OnInit {
resolveWith: (result?: UpdateProductOptionInput & { autoUpdate: boolean }) => void;
updateVariantName = true;
// Provided by caller
productOption: ProductVariant.Options;
activeLanguage: LanguageCode;
Expand All @@ -29,7 +31,7 @@ export class UpdateProductOptionDialogComponent implements Dialog<UpdateProductO

ngOnInit(): void {
const currentTranslation = this.productOption.translations.find(
(t) => t.languageCode === this.activeLanguage,
t => t.languageCode === this.activeLanguage,
);
this.name = currentTranslation?.name ?? '';
this.code = this.productOption.code;
Expand Down Expand Up @@ -64,7 +66,7 @@ export class UpdateProductOptionDialogComponent implements Dialog<UpdateProductO
name: '',
},
});
this.resolveWith(result);
this.resolveWith({ ...result, autoUpdate: this.updateVariantName });
}

cancel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DeletionResult,
FacetWithValues,
LanguageCode,
ProductWithVariants,
UpdateProductInput,
UpdateProductMutation,
UpdateProductOptionInput,
Expand All @@ -30,13 +31,13 @@ export class ProductDetailService {
constructor(private dataService: DataService) {}

getFacets(): Observable<FacetWithValues.Fragment[]> {
return this.dataService.facet.getAllFacets().mapSingle((data) => data.facets.items);
return this.dataService.facet.getAllFacets().mapSingle(data => data.facets.items);
}

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

Expand All @@ -46,14 +47,14 @@ export class ProductDetailService {
languageCode: LanguageCode,
) {
const createProduct$ = this.dataService.product.createProduct(input);
const nonEmptyOptionGroups = createVariantsConfig.groups.filter((g) => 0 < g.values.length);
const nonEmptyOptionGroups = createVariantsConfig.groups.filter(g => 0 < g.values.length);
const createOptionGroups$ = this.createProductOptionGroups(nonEmptyOptionGroups, languageCode);

return forkJoin(createProduct$, createOptionGroups$).pipe(
mergeMap(([{ createProduct }, optionGroups]) => {
const addOptionsToProduct$ = optionGroups.length
? forkJoin(
optionGroups.map((optionGroup) => {
optionGroups.map(optionGroup => {
return this.dataService.product.addOptionGroupToProduct({
productId: createProduct.id,
optionGroupId: optionGroup.id,
Expand All @@ -68,10 +69,10 @@ export class ProductDetailService {
);
}),
mergeMap(({ createProduct, optionGroups }) => {
const variants = createVariantsConfig.variants.map((v) => {
const variants = createVariantsConfig.variants.map(v => {
const optionIds = optionGroups.length
? v.optionValues.map((optionName, index) => {
const option = optionGroups[index].options.find((o) => o.name === optionName);
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`,
Expand All @@ -85,7 +86,7 @@ export class ProductDetailService {
optionIds,
};
});
const options = optionGroups.map((og) => og.options).reduce((flat, o) => [...flat, ...o], []);
const options = optionGroups.map(og => og.options).reduce((flat, o) => [...flat, ...o], []);
return this.createProductVariants(createProduct, variants, options, languageCode);
}),
);
Expand All @@ -94,17 +95,17 @@ export class ProductDetailService {
createProductOptionGroups(groups: Array<{ name: string; values: string[] }>, languageCode: LanguageCode) {
return groups.length
? forkJoin(
groups.map((c) => {
groups.map(c => {
return this.dataService.product
.createProductOptionGroups({
code: normalizeString(c.name, '-'),
translations: [{ languageCode, name: c.name }],
options: c.values.map((v) => ({
options: c.values.map(v => ({
code: normalizeString(v, '-'),
translations: [{ languageCode, name: v }],
})),
})
.pipe(map((data) => data.createProductOptionGroup));
.pipe(map(data => data.createProductOptionGroup));
}),
)
: of([]);
Expand All @@ -116,12 +117,12 @@ export class ProductDetailService {
options: Array<{ id: string; name: string }>,
languageCode: LanguageCode,
) {
const variants: CreateProductVariantInput[] = variantData.map((v) => {
const variants: CreateProductVariantInput[] = variantData.map(v => {
const name = options.length
? `${product.name} ${v.optionIds
.map((id) => options.find((o) => o.id === id))
.map(id => options.find(o => o.id === id))
.filter(notNullOrUndefined)
.map((o) => o.name)
.map(o => o.name)
.join(' ')}`
: product.name;
return {
Expand Down Expand Up @@ -157,13 +158,54 @@ export class ProductDetailService {
return forkJoin(updateOperations);
}

updateProductOption(input: UpdateProductOptionInput) {
return this.dataService.product.updateProductOption(input);
updateProductOption(
input: UpdateProductOptionInput & { autoUpdate: boolean },
product: ProductWithVariants.Fragment,
languageCode: LanguageCode,
) {
let updateProductVariantNames$: Observable<any> = of([]);
if (input.autoUpdate) {
// Update any ProductVariants' names which include the option name
let oldOptionName: string | undefined;
const newOptionName = input.translations?.find(t => t.languageCode === languageCode)?.name;
if (!newOptionName) {
updateProductVariantNames$ = of([]);
}
const variantsToUpdate: UpdateProductVariantInput[] = [];
for (const variant of product.variants) {
if (variant.options.map(o => o.id).includes(input.id)) {
if (!oldOptionName) {
oldOptionName = variant.options
.find(o => o.id === input.id)
?.translations.find(t => t.languageCode === languageCode)?.name;
}
const variantName =
variant.translations.find(t => t.languageCode === languageCode)?.name || '';
if (oldOptionName && newOptionName && variantName.includes(oldOptionName)) {
variantsToUpdate.push({
id: variant.id,
translations: [
{
languageCode,
name: variantName.replace(oldOptionName, newOptionName),
},
],
});
}
}
}
if (variantsToUpdate.length) {
updateProductVariantNames$ = this.dataService.product.updateProductVariants(variantsToUpdate);
}
}
return this.dataService.product
.updateProductOption(input)
.pipe(mergeMap(() => updateProductVariantNames$));
}

deleteProductVariant(id: string, productId: string) {
return this.dataService.product.deleteProductVariant(id).pipe(
switchMap((result) => {
switchMap(result => {
if (result.deleteProductVariant.result === DeletionResult.DELETED) {
return this.dataService.product.getProduct(productId).single$;
} else {
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Přiřadit do { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Náhled ceny v kanálu",
"collection-contents": "Obsah kolekce",
"confirm-adding-options-delete-default-body": "Přidáním vlastností k tomuto produktu způsobí vymazání nýnější výchozí varianty. Chcete pokračovat?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Zuweisen an { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Kanal-Preisvorschau",
"collection-contents": "Inhalt der Sammlung",
"confirm-adding-options-delete-default-body": "Das Hinzufügen von Optionen zu diesem Produkt führt dazu, dass die vorhandene Standardvariante gelöscht wird. Möchten Sie fortfahren?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Assign to { channelCode }",
"assign-variant-to-channel-success": "Successfully assigned product variant to \"{ channel }\"",
"assign-variants-to-channel": "Assign product variants to channel",
"auto-update-product-variant-name": "Automatically update the names of ProductVariants using this option",
"channel-price-preview": "Channel price preview",
"collection-contents": "Collection contents",
"confirm-adding-options-delete-default-body": "Adding options to this product will cause the existing default variant to be deleted. Do you wish to proceed?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Asignar a { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Vista previa de precio para el canal de ventas",
"collection-contents": "Contenidos de la colección",
"confirm-adding-options-delete-default-body": "Añadir optiones a este producto eliminará la variante por defecto. ¿Desea continuar?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Attribuer à { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Prévisualisation du prix du canal",
"collection-contents": "Contenu de la Collection",
"confirm-adding-options-delete-default-body": "L'ajout d'options à ce produit supprimera les variations existantes par défaut. Voulez-vous continuer ?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Przypisz do { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Podgląd cen kanału",
"collection-contents": "Zawartość kolekcji",
"confirm-adding-options-delete-default-body": "Dodawanie opcji spowoduje, że obecna domyślna opcja zostanie usunięta. Czy chcesz kontynuować?",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "Atribuir a { channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "Visualizar preço do canal",
"collection-contents": "Conteúdo da categoria",
"confirm-adding-options-delete-default-body": "Adicionar opções a este produto fará com que a variante padrão existente seja excluída. Você deseja continuar?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"assign-to-named-channel": "分配到{ channelCode }",
"assign-variant-to-channel-success": "",
"assign-variants-to-channel": "",
"auto-update-product-variant-name": "",
"channel-price-preview": "渠道价格预览",
"collection-contents": "系列产品",
"confirm-adding-options-delete-default-body": "添加新规格到此产品会导致含此规格的产品被删除,确认继续吗?",
Expand Down
Loading

0 comments on commit 0e98cb5

Please sign in to comment.