Browse Source

feat(admin-ui): Add support for bulk facet deletion

Relates to #853
Michael Bromley 3 years ago
parent
commit
3c6cd9bcc1

+ 2 - 0
packages/admin-ui/src/lib/catalog/src/catalog.module.ts

@@ -15,6 +15,7 @@ import { CollectionTreeNodeComponent } from './components/collection-tree/collec
 import { CollectionTreeComponent } from './components/collection-tree/collection-tree.component';
 import { CollectionTreeComponent } from './components/collection-tree/collection-tree.component';
 import { ConfirmVariantDeletionDialogComponent } from './components/confirm-variant-deletion-dialog/confirm-variant-deletion-dialog.component';
 import { ConfirmVariantDeletionDialogComponent } from './components/confirm-variant-deletion-dialog/confirm-variant-deletion-dialog.component';
 import { FacetDetailComponent } from './components/facet-detail/facet-detail.component';
 import { FacetDetailComponent } from './components/facet-detail/facet-detail.component';
+import { deleteFacetsBulkAction } from './components/facet-list/facet-list-bulk-actions';
 import { FacetListComponent } from './components/facet-list/facet-list.component';
 import { FacetListComponent } from './components/facet-list/facet-list.component';
 import { GenerateProductVariantsComponent } from './components/generate-product-variants/generate-product-variants.component';
 import { GenerateProductVariantsComponent } from './components/generate-product-variants/generate-product-variants.component';
 import { OptionValueInputComponent } from './components/option-value-input/option-value-input.component';
 import { OptionValueInputComponent } from './components/option-value-input/option-value-input.component';
@@ -70,5 +71,6 @@ export class CatalogModule {
         bulkActionRegistryService.registerBulkAction(assignFacetValuesToProductsBulkAction);
         bulkActionRegistryService.registerBulkAction(assignFacetValuesToProductsBulkAction);
         bulkActionRegistryService.registerBulkAction(assignProductsToChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(assignProductsToChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(deleteProductsBulkAction);
         bulkActionRegistryService.registerBulkAction(deleteProductsBulkAction);
+        bulkActionRegistryService.registerBulkAction(deleteFacetsBulkAction);
     }
     }
 }
 }

+ 126 - 0
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list-bulk-actions.ts

@@ -0,0 +1,126 @@
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { FacetListComponent } from '@vendure/admin-ui/catalog';
+import {
+    BulkAction,
+    DataService,
+    DeletionResult,
+    GetFacetList,
+    ModalService,
+    NotificationService,
+} from '@vendure/admin-ui/core';
+import { unique } from '@vendure/common/lib/unique';
+import { EMPTY, of } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+
+export const deleteFacetsBulkAction: BulkAction<GetFacetList.Items, FacetListComponent> = {
+    location: 'facet-list',
+    label: _('common.delete'),
+    icon: 'trash',
+    iconClass: 'is-danger',
+    onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+        const modalService = injector.get(ModalService);
+        const dataService = injector.get(DataService);
+        const notificationService = injector.get(NotificationService);
+
+        function showModalAndDelete(facetIds: string[], message?: string) {
+            return modalService
+                .dialog({
+                    title: _('catalog.confirm-bulk-delete-facets'),
+                    translationVars: {
+                        count: selection.length,
+                    },
+                    size: message ? 'lg' : 'md',
+                    body: message,
+                    buttons: [
+                        { type: 'secondary', label: _('common.cancel') },
+                        {
+                            type: 'danger',
+                            label: message ? _('common.force-delete') : _('common.delete'),
+                            returnValue: true,
+                        },
+                    ],
+                })
+                .pipe(
+                    switchMap(res =>
+                        res
+                            ? dataService.facet
+                                  .deleteFacets(facetIds, !!message)
+                                  .pipe(map(res2 => res2.deleteFacets))
+                            : of([]),
+                    ),
+                );
+        }
+
+        showModalAndDelete(unique(selection.map(f => f.id)))
+            .pipe(
+                switchMap(result => {
+                    let deletedCount = 0;
+                    const errors: string[] = [];
+                    const errorIds: string[] = [];
+                    let i = 0;
+                    for (const item of result) {
+                        if (item.result === DeletionResult.DELETED) {
+                            deletedCount++;
+                        } else if (item.message) {
+                            errors.push(item.message);
+                            errorIds.push(selection[i]?.id);
+                        }
+                        i++;
+                    }
+                    if (0 < errorIds.length) {
+                        return showModalAndDelete(errorIds, errors.join('\n')).pipe(
+                            map(result2 => {
+                                const deletedCount2 = result2.filter(
+                                    r => r.result === DeletionResult.DELETED,
+                                ).length;
+                                return deletedCount + deletedCount2;
+                            }),
+                        );
+                    } else {
+                        return of(deletedCount);
+                    }
+                }),
+            )
+            .subscribe(deletedCount => {
+                if (deletedCount) {
+                    hostComponent.refresh();
+                    clearSelection();
+                    notificationService.success(_('common.notify-bulk-delete-success'), {
+                        count: deletedCount,
+                        entity: 'Facets',
+                    });
+                }
+            });
+    },
+};
+
+// export const assignProductsToChannelBulkAction: BulkAction<SearchProducts.Items, ProductListComponent> = {
+//     location: 'product-list',
+//     label: _('catalog.assign-to-channel'),
+//     icon: 'layers',
+//     isVisible: ({ injector }) => {
+//         return injector
+//             .get(DataService)
+//             .client.userStatus()
+//             .mapSingle(({ userStatus }) => 1 < userStatus.channels.length)
+//             .toPromise();
+//     },
+//     onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+//         const modalService = injector.get(ModalService);
+//         const dataService = injector.get(DataService);
+//         const notificationService = injector.get(NotificationService);
+//         modalService
+//             .fromComponent(AssignProductsToChannelDialogComponent, {
+//                 size: 'lg',
+//                 locals: {
+//                     productIds: unique(selection.map(p => p.productId)),
+//                     currentChannelIds: [],
+//                 },
+//             })
+//             .subscribe(result => {
+//                 if (result) {
+//                     clearSelection();
+//                 }
+//             });
+//     },
+// };

+ 6 - 0
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.html

@@ -35,7 +35,13 @@
     [currentPage]="currentPage$ | async"
     [currentPage]="currentPage$ | async"
     (pageChange)="setPageNumber($event)"
     (pageChange)="setPageNumber($event)"
     (itemsPerPageChange)="setItemsPerPage($event)"
     (itemsPerPageChange)="setItemsPerPage($event)"
+    [selectionManager]="selectionManager"
 >
 >
+    <vdr-bulk-action-menu
+        locationId="facet-list"
+        [hostComponent]="this"
+        [selectionManager]="selectionManager"
+    ></vdr-bulk-action-menu>
     <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
     <vdr-dt-column [expand]="true">{{ 'catalog.values' | translate }}</vdr-dt-column>
     <vdr-dt-column [expand]="true">{{ 'catalog.values' | translate }}</vdr-dt-column>

+ 13 - 1
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.ts

@@ -10,6 +10,7 @@ import {
     LanguageCode,
     LanguageCode,
     ModalService,
     ModalService,
     NotificationService,
     NotificationService,
+    SelectionManager,
     ServerConfigService,
     ServerConfigService,
 } from '@vendure/admin-ui/core';
 } from '@vendure/admin-ui/core';
 import { SortOrder } from '@vendure/common/lib/generated-shop-types';
 import { SortOrder } from '@vendure/common/lib/generated-shop-types';
@@ -30,6 +31,8 @@ export class FacetListComponent
     contentLanguage$: Observable<LanguageCode>;
     contentLanguage$: Observable<LanguageCode>;
     readonly initialLimit = 3;
     readonly initialLimit = 3;
     displayLimit: { [id: string]: number } = {};
     displayLimit: { [id: string]: number } = {};
+    selectionManager: SelectionManager<GetFacetList.Items>;
+
     constructor(
     constructor(
         private dataService: DataService,
         private dataService: DataService,
         private modalService: ModalService,
         private modalService: ModalService,
@@ -57,6 +60,11 @@ export class FacetListComponent
                 },
                 },
             }),
             }),
         );
         );
+        this.selectionManager = new SelectionManager<GetFacetList.Items>({
+            multiSelect: true,
+            itemsAreEqual: (a, b) => a.id === b.id,
+            additiveMode: true,
+        });
     }
     }
 
 
     ngOnInit() {
     ngOnInit() {
@@ -124,7 +132,11 @@ export class FacetListComponent
                 body: message,
                 body: message,
                 buttons: [
                 buttons: [
                     { type: 'secondary', label: _('common.cancel') },
                     { type: 'secondary', label: _('common.cancel') },
-                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                    {
+                        type: 'danger',
+                        label: message ? _('common.force-delete') : _('common.delete'),
+                        returnValue: true,
+                    },
                 ],
                 ],
             })
             })
             .pipe(
             .pipe(

+ 25 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -2447,6 +2447,8 @@ export type Mutation = {
   deleteFacet: DeletionResponse;
   deleteFacet: DeletionResponse;
   /** Delete one or more FacetValues */
   /** Delete one or more FacetValues */
   deleteFacetValues: Array<DeletionResponse>;
   deleteFacetValues: Array<DeletionResponse>;
+  /** Delete multiple existing Facets */
+  deleteFacets: Array<DeletionResponse>;
   deleteOrderNote: DeletionResponse;
   deleteOrderNote: DeletionResponse;
   /** Delete a PaymentMethod */
   /** Delete a PaymentMethod */
   deletePaymentMethod: DeletionResponse;
   deletePaymentMethod: DeletionResponse;
@@ -2845,6 +2847,12 @@ export type MutationDeleteFacetValuesArgs = {
 };
 };
 
 
 
 
+export type MutationDeleteFacetsArgs = {
+  ids: Array<Scalars['ID']>;
+  force?: Maybe<Scalars['Boolean']>;
+};
+
+
 export type MutationDeleteOrderNoteArgs = {
 export type MutationDeleteOrderNoteArgs = {
   id: Scalars['ID'];
   id: Scalars['ID'];
 };
 };
@@ -6366,6 +6374,17 @@ export type DeleteFacetMutation = { deleteFacet: (
     & Pick<DeletionResponse, 'result' | 'message'>
     & Pick<DeletionResponse, 'result' | 'message'>
   ) };
   ) };
 
 
+export type DeleteFacetsMutationVariables = Exact<{
+  ids: Array<Scalars['ID']> | Scalars['ID'];
+  force?: Maybe<Scalars['Boolean']>;
+}>;
+
+
+export type DeleteFacetsMutation = { deleteFacets: Array<(
+    { __typename?: 'DeletionResponse' }
+    & Pick<DeletionResponse, 'result' | 'message'>
+  )> };
+
 export type CreateFacetValuesMutationVariables = Exact<{
 export type CreateFacetValuesMutationVariables = Exact<{
   input: Array<CreateFacetValueInput> | CreateFacetValueInput;
   input: Array<CreateFacetValueInput> | CreateFacetValueInput;
 }>;
 }>;
@@ -9873,6 +9892,12 @@ export namespace DeleteFacet {
   export type DeleteFacet = (NonNullable<DeleteFacetMutation['deleteFacet']>);
   export type DeleteFacet = (NonNullable<DeleteFacetMutation['deleteFacet']>);
 }
 }
 
 
+export namespace DeleteFacets {
+  export type Variables = DeleteFacetsMutationVariables;
+  export type Mutation = DeleteFacetsMutation;
+  export type DeleteFacets = NonNullable<(NonNullable<DeleteFacetsMutation['deleteFacets']>)[number]>;
+}
+
 export namespace CreateFacetValues {
 export namespace CreateFacetValues {
   export type Variables = CreateFacetValuesMutationVariables;
   export type Variables = CreateFacetValuesMutationVariables;
   export type Mutation = CreateFacetValuesMutation;
   export type Mutation = CreateFacetValuesMutation;

+ 9 - 0
packages/admin-ui/src/lib/core/src/data/definitions/facet-definitions.ts

@@ -70,6 +70,15 @@ export const DELETE_FACET = gql`
     }
     }
 `;
 `;
 
 
+export const DELETE_FACETS = gql`
+    mutation DeleteFacets($ids: [ID!]!, $force: Boolean) {
+        deleteFacets(ids: $ids, force: $force) {
+            result
+            message
+        }
+    }
+`;
+
 export const CREATE_FACET_VALUES = gql`
 export const CREATE_FACET_VALUES = gql`
     mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
     mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
         createFacetValues(input: $input) {
         createFacetValues(input: $input) {

+ 9 - 0
packages/admin-ui/src/lib/core/src/data/providers/facet-data.service.ts

@@ -6,6 +6,7 @@ import {
     CreateFacetValueInput,
     CreateFacetValueInput,
     CreateFacetValues,
     CreateFacetValues,
     DeleteFacet,
     DeleteFacet,
+    DeleteFacets,
     DeleteFacetValues,
     DeleteFacetValues,
     GetFacetList,
     GetFacetList,
     GetFacetWithValues,
     GetFacetWithValues,
@@ -19,6 +20,7 @@ import {
     CREATE_FACET_VALUES,
     CREATE_FACET_VALUES,
     DELETE_FACET,
     DELETE_FACET,
     DELETE_FACET_VALUES,
     DELETE_FACET_VALUES,
+    DELETE_FACETS,
     GET_FACET_LIST,
     GET_FACET_LIST,
     GET_FACET_WITH_VALUES,
     GET_FACET_WITH_VALUES,
     UPDATE_FACET,
     UPDATE_FACET,
@@ -73,6 +75,13 @@ export class FacetDataService {
         });
         });
     }
     }
 
 
+    deleteFacets(ids: string[], force: boolean) {
+        return this.baseDataService.mutate<DeleteFacets.Mutation, DeleteFacets.Variables>(DELETE_FACETS, {
+            ids,
+            force,
+        });
+    }
+
     createFacetValues(facetValues: CreateFacetValueInput[]) {
     createFacetValues(facetValues: CreateFacetValueInput[]) {
         const input: CreateFacetValues.Variables = {
         const input: CreateFacetValues.Variables = {
             input: facetValues.map(pick(['facetId', 'code', 'translations', 'customFields'])),
             input: facetValues.map(pick(['facetId', 'code', 'translations', 'customFields'])),

+ 1 - 1
packages/admin-ui/src/lib/core/src/providers/bulk-action-registry/bulk-action-types.ts

@@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
  * @docsCategory bulk-actions
  * @docsCategory bulk-actions
  * @docsPage BulkAction
  * @docsPage BulkAction
  */
  */
-export type BulkActionLocationId = 'product-list' | 'order-list' | string;
+export type BulkActionLocationId = 'product-list' | 'facet-list' | 'order-list' | string;
 
 
 export interface BulkActionIsVisibleContext<ItemType, ComponentType> {
 export interface BulkActionIsVisibleContext<ItemType, ComponentType> {
     /**
     /**

+ 2 - 0
packages/admin-ui/src/lib/core/src/providers/modal/modal.service.ts

@@ -43,6 +43,7 @@ export interface DialogConfig<T> {
     body?: string;
     body?: string;
     translationVars?: { [key: string]: string | number };
     translationVars?: { [key: string]: string | number };
     buttons: Array<DialogButtonConfig<T>>;
     buttons: Array<DialogButtonConfig<T>>;
+    size?: 'sm' | 'md' | 'lg' | 'xl';
 }
 }
 
 
 /**
 /**
@@ -166,6 +167,7 @@ export class ModalService {
     dialog<T>(config: DialogConfig<T>): Observable<T | undefined> {
     dialog<T>(config: DialogConfig<T>): Observable<T | undefined> {
         return this.fromComponent(SimpleDialogComponent, {
         return this.fromComponent(SimpleDialogComponent, {
             locals: config,
             locals: config,
+            size: config.size,
         });
         });
     }
     }
 }
 }

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/simple-dialog/simple-dialog.component.html

@@ -1,5 +1,5 @@
 <ng-template vdrDialogTitle>{{ title | translate:translationVars }}</ng-template>
 <ng-template vdrDialogTitle>{{ title | translate:translationVars }}</ng-template>
-{{ body | translate:translationVars }}
+<div style="white-space: pre-wrap;">{{ body | translate:translationVars }}</div>
 <ng-template vdrDialogButtons>
 <ng-template vdrDialogButtons>
     <ng-container *ngFor="let button of buttons">
     <ng-container *ngFor="let button of buttons">
         <button
         <button