Jelajahi Sumber

refactor(admin-ui): Make server config injectable

Michael Bromley 7 tahun lalu
induk
melakukan
e6ed20b64e

+ 3 - 1
admin-ui/src/app/administrator/components/admin-detail/admin-detail.component.ts

@@ -16,6 +16,7 @@ import { BaseDetailComponent } from '../../../common/base-detail.component';
 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';
 
 @Component({
     selector: 'vdr-admin-detail',
@@ -33,12 +34,13 @@ export class AdminDetailComponent extends BaseDetailComponent<Administrator> imp
     constructor(
         router: Router,
         route: ActivatedRoute,
+        serverConfigService: ServerConfigService,
         private changeDetector: ChangeDetectorRef,
         private dataService: DataService,
         private formBuilder: FormBuilder,
         private notificationService: NotificationService,
     ) {
-        super(route, router);
+        super(route, router, serverConfigService);
         this.administratorForm = this.formBuilder.group({
             emailAddress: ['', Validators.required],
             firstName: ['', Validators.required],

+ 3 - 1
admin-ui/src/app/administrator/components/role-detail/role-detail.component.ts

@@ -10,6 +10,7 @@ import { BaseDetailComponent } from '../../../common/base-detail.component';
 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';
 
 @Component({
     selector: 'vdr-role-detail',
@@ -25,12 +26,13 @@ export class RoleDetailComponent extends BaseDetailComponent<Role> implements On
     constructor(
         router: Router,
         route: ActivatedRoute,
+        serverConfigService: ServerConfigService,
         private changeDetector: ChangeDetectorRef,
         private dataService: DataService,
         private formBuilder: FormBuilder,
         private notificationService: NotificationService,
     ) {
-        super(route, router);
+        super(route, router, serverConfigService);
         this.permissions = Object.keys(Permission).reduce(
             (result, key) => ({ ...result, [key]: false }),
             {} as { [K in Permission]: boolean },

+ 3 - 1
admin-ui/src/app/catalog/components/facet-detail/facet-detail.component.ts

@@ -19,6 +19,7 @@ import { createUpdatedTranslatable } from '../../../common/utilities/create-upda
 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';
 
 @Component({
     selector: 'vdr-facet-detail',
@@ -36,12 +37,13 @@ export class FacetDetailComponent extends BaseDetailComponent<FacetWithValues> i
     constructor(
         router: Router,
         route: ActivatedRoute,
+        serverConfigService: ServerConfigService,
         private changeDetector: ChangeDetectorRef,
         private dataService: DataService,
         private formBuilder: FormBuilder,
         private notificationService: NotificationService,
     ) {
-        super(route, router);
+        super(route, router, serverConfigService);
         this.customFields = this.getCustomFieldConfig('Facet');
         this.customValueFields = this.getCustomFieldConfig('FacetValue');
         this.facetForm = this.formBuilder.group({

+ 3 - 1
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -20,6 +20,7 @@ import { createUpdatedTranslatable } from '../../../common/utilities/create-upda
 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 { ApplyFacetDialogComponent } from '../apply-facet-dialog/apply-facet-dialog.component';
 
@@ -41,12 +42,13 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
     constructor(
         route: ActivatedRoute,
         router: Router,
+        serverConfigService: ServerConfigService,
         private dataService: DataService,
         private formBuilder: FormBuilder,
         private modalService: ModalService,
         private notificationService: NotificationService,
     ) {
-        super(route, router);
+        super(route, router, serverConfigService);
         this.customFields = this.getCustomFieldConfig('Product');
         this.customVariantFields = this.getCustomFieldConfig('ProductVariant');
         this.productForm = this.formBuilder.group({

+ 7 - 7
admin-ui/src/app/common/base-detail.component.ts

@@ -1,14 +1,10 @@
-import { ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
-import { FormBuilder } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, Observable, of, Subject } from 'rxjs';
 import { map, switchMap, takeUntil } from 'rxjs/operators';
 import { LanguageCode } from 'shared/generated-types';
 import { CustomFieldConfig, CustomFields } from 'shared/shared-types';
 
-import { NotificationService } from '../core/providers/notification/notification.service';
-import { DataService } from '../data/providers/data.service';
-import { getServerConfig } from '../data/server-config';
+import { ServerConfigService } from '../data/server-config';
 
 import { getDefaultLanguage } from './utilities/get-default-language';
 
@@ -19,7 +15,11 @@ export abstract class BaseDetailComponent<Entity extends { id: string }> {
     isNew$: Observable<boolean>;
     protected destroy$ = new Subject<void>();
 
-    protected constructor(protected route: ActivatedRoute, protected router: Router) {}
+    protected constructor(
+        protected route: ActivatedRoute,
+        protected router: Router,
+        private serverConfigService: ServerConfigService,
+    ) {}
 
     init() {
         this.entity$ = this.route.data.pipe(switchMap(data => data.entity));
@@ -44,7 +44,7 @@ export abstract class BaseDetailComponent<Entity extends { id: string }> {
     protected abstract setFormValues(entity: Entity, languageCode: LanguageCode): void;
 
     protected getCustomFieldConfig(key: keyof CustomFields): CustomFieldConfig[] {
-        return getServerConfig().customFields[key] || [];
+        return this.serverConfigService.serverConfig.customFields[key] || [];
     }
 
     protected setQueryParam(key: string, value: any) {

+ 1 - 8
admin-ui/src/app/data/add-custom-fields.ts

@@ -9,18 +9,11 @@ import {
 
 import { CustomFields } from 'shared/shared-types';
 
-import { getServerConfig } from './server-config';
-
 /**
  * Given a GraphQL AST (DocumentNode), this function looks for fragment definitions and adds and configured
  * custom fields to those fragments.
  */
-export function addCustomFields(
-    documentNode: DocumentNode,
-    providedCustomFields?: CustomFields,
-): DocumentNode {
-    const customFields = providedCustomFields || getServerConfig().customFields;
-
+export function addCustomFields(documentNode: DocumentNode, customFields: CustomFields): DocumentNode {
     const fragmentDefs = documentNode.definitions.filter(isFragmentDefinition);
 
     for (const fragmentDef of fragmentDefs) {

+ 5 - 4
admin-ui/src/app/data/data.module.ts

@@ -1,6 +1,6 @@
 import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
 import { APP_INITIALIZER, NgModule } from '@angular/core';
-import { Apollo, APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
+import { APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
 import { InMemoryCache } from 'apollo-cache-inmemory';
 import { ApolloClientOptions } from 'apollo-client';
 import { ApolloLink } from 'apollo-link';
@@ -20,7 +20,7 @@ import { BaseDataService } from './providers/base-data.service';
 import { DataService } from './providers/data.service';
 import { FetchAdapter } from './providers/fetch-adapter';
 import { DefaultInterceptor } from './providers/interceptor';
-import { loadServerConfigFactory } from './server-config';
+import { initializeServerConfigService, ServerConfigService } from './server-config';
 
 const apolloCache = new InMemoryCache();
 
@@ -79,6 +79,7 @@ export function createApollo(
         BaseDataService,
         DataService,
         FetchAdapter,
+        ServerConfigService,
         {
             provide: APOLLO_OPTIONS,
             useFactory: createApollo,
@@ -88,8 +89,8 @@ export function createApollo(
         {
             provide: APP_INITIALIZER,
             multi: true,
-            useFactory: loadServerConfigFactory,
-            deps: [Apollo],
+            useFactory: initializeServerConfigService,
+            deps: [ServerConfigService],
         },
     ],
 })

+ 9 - 0
admin-ui/src/app/data/definitions/config-definitions.ts

@@ -0,0 +1,9 @@
+import gql from 'graphql-tag';
+
+export const GET_SERVER_CONFIG = gql`
+    query GetServerConfig {
+        config {
+            customFields
+        }
+    }
+`;

+ 12 - 2
admin-ui/src/app/data/providers/base-data.service.ts

@@ -7,10 +7,13 @@ import { ExecutionResult } from 'apollo-link';
 import { DocumentNode } from 'graphql/language/ast';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
+import { CustomFields } from 'shared/shared-types';
 
 import { API_URL } from '../../app.config';
 import { LocalStorageService } from '../../core/providers/local-storage/local-storage.service';
+import { addCustomFields } from '../add-custom-fields';
 import { QueryResult } from '../query-result';
+import { ServerConfigService } from '../server-config';
 
 /**
  * Make the MutationUpdaterFn type-safe until this issue is resolved: https://github.com/apollographql/apollo-link/issues/616
@@ -27,8 +30,13 @@ export class BaseDataService {
         private apollo: Apollo,
         private httpClient: HttpClient,
         private localStorageService: LocalStorageService,
+        private serverConfigService: ServerConfigService,
     ) {}
 
+    private get customFields(): CustomFields {
+        return this.serverConfigService.serverConfig.customFields || {};
+    }
+
     /**
      * Performs a GraphQL watch query
      */
@@ -37,8 +45,9 @@ export class BaseDataService {
         variables?: V,
         fetchPolicy: FetchPolicy = 'cache-and-network',
     ): QueryResult<T, V> {
+        const withCustomFields = addCustomFields(query, this.customFields);
         const queryRef = this.apollo.watchQuery<T, V>({
-            query,
+            query: withCustomFields,
             variables,
             fetchPolicy,
         });
@@ -53,9 +62,10 @@ export class BaseDataService {
         variables?: V,
         update?: TypedMutationUpdateFn<T>,
     ): Observable<T> {
+        const withCustomFields = addCustomFields(mutation, this.customFields);
         return this.apollo
             .mutate<T, V>({
-                mutation,
+                mutation: withCustomFields,
                 variables,
                 update: update as any,
             })

+ 5 - 11
admin-ui/src/app/data/providers/facet-data.service.ts

@@ -48,7 +48,7 @@ export class FacetDataService {
 
     getFacet(id: string): QueryResult<GetFacetWithValues, GetFacetWithValuesVariables> {
         return this.baseDataService.query<GetFacetWithValues, GetFacetWithValuesVariables>(
-            addCustomFields(GET_FACET_WITH_VALUES),
+            GET_FACET_WITH_VALUES,
             {
                 id,
                 languageCode: getDefaultLanguage(),
@@ -60,20 +60,14 @@ export class FacetDataService {
         const input: CreateFacetVariables = {
             input: pick(facet, ['code', 'translations', 'values', 'customFields']),
         };
-        return this.baseDataService.mutate<CreateFacet, CreateFacetVariables>(
-            addCustomFields(CREATE_FACET),
-            input,
-        );
+        return this.baseDataService.mutate<CreateFacet, CreateFacetVariables>(CREATE_FACET, input);
     }
 
     updateFacet(facet: UpdateFacetInput): Observable<UpdateFacet> {
         const input: UpdateFacetVariables = {
             input: pick(facet, ['id', 'code', 'translations', 'customFields']),
         };
-        return this.baseDataService.mutate<UpdateFacet, UpdateFacetVariables>(
-            addCustomFields(UPDATE_FACET),
-            input,
-        );
+        return this.baseDataService.mutate<UpdateFacet, UpdateFacetVariables>(UPDATE_FACET, input);
     }
 
     createFacetValues(facetValues: CreateFacetValueInput[]): Observable<CreateFacetValues> {
@@ -81,7 +75,7 @@ export class FacetDataService {
             input: facetValues.map(pick(['facetId', 'code', 'translations', 'customFields'])),
         };
         return this.baseDataService.mutate<CreateFacetValues, CreateFacetValuesVariables>(
-            addCustomFields(CREATE_FACET_VALUES),
+            CREATE_FACET_VALUES,
             input,
         );
     }
@@ -91,7 +85,7 @@ export class FacetDataService {
             input: facetValues.map(pick(['id', 'code', 'translations', 'customFields'])),
         };
         return this.baseDataService.mutate<UpdateFacetValues, UpdateFacetValuesVariables>(
-            addCustomFields(UPDATE_FACET_VALUES),
+            UPDATE_FACET_VALUES,
             input,
         );
     }

+ 3 - 9
admin-ui/src/app/data/providers/product-data.service.ts

@@ -69,7 +69,7 @@ export class ProductDataService {
 
     getProduct(id: string): QueryResult<GetProductWithVariants, GetProductWithVariantsVariables> {
         return this.baseDataService.query<GetProductWithVariants, GetProductWithVariantsVariables>(
-            addCustomFields(GET_PRODUCT_WITH_VARIANTS),
+            GET_PRODUCT_WITH_VARIANTS,
             {
                 id,
                 languageCode: getDefaultLanguage(),
@@ -81,20 +81,14 @@ export class ProductDataService {
         const input: CreateProductVariables = {
             input: pick(product, ['translations', 'customFields', 'assetIds', 'featuredAssetId']),
         };
-        return this.baseDataService.mutate<CreateProduct, CreateProductVariables>(
-            addCustomFields(CREATE_PRODUCT),
-            input,
-        );
+        return this.baseDataService.mutate<CreateProduct, CreateProductVariables>(CREATE_PRODUCT, input);
     }
 
     updateProduct(product: UpdateProductInput): Observable<UpdateProduct> {
         const input: UpdateProductVariables = {
             input: pick(product, ['id', 'translations', 'customFields', 'assetIds', 'featuredAssetId']),
         };
-        return this.baseDataService.mutate<UpdateProduct, UpdateProductVariables>(
-            addCustomFields(UPDATE_PRODUCT),
-            input,
-        );
+        return this.baseDataService.mutate<UpdateProduct, UpdateProductVariables>(UPDATE_PRODUCT, input);
     }
 
     generateProductVariants(

+ 32 - 36
admin-ui/src/app/data/server-config.ts

@@ -1,45 +1,41 @@
-import { Apollo } from 'apollo-angular';
-import gql from 'graphql-tag';
+import { Injectable, Injector } from '@angular/core';
+import { GetServerConfig, GetServerConfig_config } from 'shared/generated-types';
 
-import { CustomFields } from 'shared/shared-types';
+import { GET_SERVER_CONFIG } from './definitions/config-definitions';
+import { BaseDataService } from './providers/base-data.service';
 
-export interface ServerConfig {
-    customFields: CustomFields;
-}
+export type ServerConfig = GetServerConfig_config;
 
-export interface GetConfigResponse {
-    config: ServerConfig;
+export function initializeServerConfigService(serverConfigService: ServerConfigService): () => Promise<any> {
+    return serverConfigService.init();
 }
 
-let serverConfig: ServerConfig;
-
 /**
- * Fetches the ServerConfig. Should be run as part of the app bootstrap process by attaching
- * to the Angular APP_INITIALIZER token.
+ * A service which fetches the config from the server upon initialization, and then provides that config
+ * to the components which require it.
  */
-export function loadServerConfigFactory(apollo: Apollo): () => Promise<ServerConfig> {
-    return () => {
-        // TODO: usethe gql tag function once graphql-js 14.0.0 is released
-        // issue: https://github.com/graphql/graphql-js/issues/1344
-        // note: the rc of 14.0.0 does not work with the apollo-cli used for codegen.
-        // Test this when upgrading.
-        const query = gql`
-            query GetConfig {
-                config {
-                    customFields
-                }
-            }
-        `;
-        return apollo
-            .query<GetConfigResponse>({ query })
-            .toPromise()
-            .then(result => {
-                serverConfig = result.data.config;
-                return serverConfig;
-            });
-    };
-}
+@Injectable()
+export class ServerConfigService {
+    private _serverConfig: ServerConfig = {} as any;
+
+    constructor(private injector: Injector) {}
+
+    /**
+     * Fetches the ServerConfig. Should be run as part of the app bootstrap process by attaching
+     * to the Angular APP_INITIALIZER token.
+     */
+    init(): () => Promise<any> {
+        const baseDataService = this.injector.get<BaseDataService>(BaseDataService);
+        return () =>
+            baseDataService
+                .query<GetServerConfig>(GET_SERVER_CONFIG)
+                .single$.toPromise()
+                .then(result => {
+                    this._serverConfig = result.config;
+                });
+    }
 
-export function getServerConfig(): ServerConfig {
-    return serverConfig;
+    get serverConfig(): ServerConfig {
+        return this._serverConfig;
+    }
 }

+ 16 - 0
shared/generated-types.ts

@@ -402,6 +402,22 @@ export interface GetCurrentUser {
 /* tslint:disable */
 // This file was automatically generated and should not be edited.
 
+// ====================================================
+// GraphQL query operation: GetServerConfig
+// ====================================================
+
+export interface GetServerConfig_config {
+  __typename: "Config";
+  customFields: any | null;
+}
+
+export interface GetServerConfig {
+  config: GetServerConfig_config;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
 // ====================================================
 // GraphQL mutation operation: CreateFacet
 // ====================================================