vendure-ecommerce / vendure

The commerce platform with customization in its DNA.
https://www.vendure.io
Other
5.77k stars 1.02k forks source link

Admin UI Can not editing collection while customized fields with relation type (with translations) #1338

Open tianyingchun opened 2 years ago

tianyingchun commented 2 years ago

Describe the bug Can not editing collection while customized fields with relation type (with translations)

To Reproduce Steps to reproduce the behavior:

  1. extends new fields named campaign (relation type) on collection as below
    
    {
    type: 'relation',
    name: 'campaign',
    nullable: true,
    entity: Campaign,
    eager: true,
    public: true,
    label: [
      {
        languageCode: LanguageCode.en,
        value: 'Campaign',
      },
    ],
    description: [
      {
        languageCode: LanguageCode.en,
        value: 'Campaign of this collection page',
      },
    ],
    },
    config.customFields.Collection.push(...x);

2. setup collection  entity field(`campaignId`) with valid `ID` of `Campaign` entity.
3. Go to `admin collection page `catalog/collections`  click editing button to ready redirect to `collection` detail page.
4. throw error `Cannot return null for non-nullable field Campaign.name.` 

**Expected behavior**
the Campaign entity as below
```ts
@Entity('campaign')
export class Campaign extends VendureEntity {
  constructor(input?: DeepPartial<Campaign>) {
    super(input);
  }

  @Column('varchar')
  campaignType: CampaignType;

  @Column({ nullable: true })
  periodOfValidity: number;

  @Column({ unique: true })
  code: string;

  name: LocaleString;

  applyForBeforeData: LocaleString;

  applyForResultData: LocaleString;

  @Column({ default: true })
  enabled: boolean;

  @ManyToOne(() => Promotion, { onDelete: 'SET NULL' })
  promotion: Promotion | null;

  @Column('int', { nullable: true })
  promotionId: ID | null;

  @OneToMany(() => CampaignTranslation, (translation) => translation.base, { eager: true })
  translations: Array<Translation<CampaignTranslation>>;
}
@Entity('campaign_translation')
export class CampaignTranslation extends VendureEntity implements Translation<Campaign> {
  constructor(input?: DeepPartial<Translation<CampaignTranslation>>) {
    super(input);
  }
  @Column('varchar')
  languageCode: LanguageCode;

  @Column('varchar')
  name: string;

  @Column('simple-json')
  applyForBeforeData: any;

  @Column('simple-json')
  applyForResultData: any;

  @ManyToOne(() => Campaign, (base) => base.translations, { onDelete: 'CASCADE' })
  base: Campaign;
}

Environment (please complete the following information):

Additional context for Admin UI /catalog/collections Editing button before redirect to collect detail page.

  type Campaign implements Node {
    id: ID!
    createdAt: DateTime!
    updatedAt: DateTime!
   // here if we expose name as `optional`, the  admin works fine, other wise throw me error.
    name: String!
    code: String!
    promotion: Promotion
    periodOfValidity: Int
    enabled: Boolean!
    campaignType: CampaignType!
    applyForBeforeData: CampaignApplyForBeforeData!
    applyForResultData: CampaignApplyForResultData!
  }

If we expose graphql Campaign property name as optional,( name: String!)==> (name: String) the admin works fine, other wise throw me error.

jnugh commented 1 year ago

We are also experiencing this issue. When data is being loaded ListQueryBuilder the method parallelLoadRelations joins customFields.[translatableRelationName] but not the nested translations. If the property was null, the relation would be loaded here: https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/src/api/config/generate-resolvers.ts#L184 which happens when single entities are being loaded.

I see two possible angles to fix this and would be open to create a PR.

  1. It would be possible to fix the entity in the customFieldResolver by detecting if a language relation exists in the TypeORM metadata. If the property is null in this case the entity should be reloaded using the existing functionality.
  2. Find a way to load and populate the translations in ListQueryBuilder - however as I understand it should be on the same abstraction level as TypeORM so it should probably not include logic for language.

As I already worked on solution 1 while debugging I will create a PR based on that idea. But if you see a better way or prefer solution 2 please let me know.

jnugh commented 1 year ago

While writing a test using default entities I noticed that this issue does not occur on stock entities. There are already field resolvers in place for this case. I just had to add them to our custom entities. 🤦🏽‍♂️

e.g. the product resolver has:

 @ResolveField()
    name(@Ctx() ctx: RequestContext, @Parent() product: Product): Promise<string> {
        return this.localeStringHydrator.hydrateLocaleStringField(ctx, product, 'name');
    }