vendure-ecommerce / vendure

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

Documentation improvements #2299

Closed michaelbromley closed 4 months ago

michaelbromley commented 1 year ago

Background

Vendure is dedicated to providing an outstanding developer experience. A major part of that is our documentation at https://docs.vendure.io. The docs have a solid foundation of correctness (all API docs are generated from source in a readable, linked way) and coverage in the form of many pages of hand-written guides.

However, in the spirit of continual improvement, I am launching an effort to further improve the documentation with the following goals (in order of priority):

Improving information architecture

Right now we have a single navbar on the left with a tree structure. There is a visual break between the hand-written guides and the generated API docs: image

I think we can improve the information architecture to something along the lines of the new React docs: https://react.dev/learn

They make the top-level split between "learn" (guides) and "reference" (API docs).

Within "learn" they have a "get started" and "learn react" sub-sections, and then these break down into the major conceptual areas: "describing the ui", "adding interactivity" etc. Each topic is a very comprehensive, tutorial-like article:

image

In our case, the "learn vendure" topic areas could be something like:

The "administrator guide" which contains instructions for using the Admin UI could be moved to a different section, e.g. "operator's manual".

Task-focused

We should move towards a more task-focused approach. There are common tasks that devs want to complete with Vendure - we need to anticipate these and provide a complete, step-by-step guide to doing it, including copy-pastable code snippets.

More complex tasks that require many related parts in different files can be complemented with an actual working plugin in the dev-server/documentation-examples directory.

Improved search

Feedback from a user:

I’d like to make a case for better search. I think the documentation has most of the information required to customize Vendure.

However, the documentation is laid out based on code and objects which means that I have to know Vendure and specific language to use the documentation which is counter intuitive.

We can improve search using keywords, and perhaps a full-on search engine rather than the client-side index search we currently have.

michaelbromley commented 1 year ago

I can only agree to the things said above. One thing I would like to add is some kind of "Nest.js in the context of Vendure". It took me a long time to understand these decorators and the architecture with resolvers, modules, providers and services. Such a video or written guide would help immensely in the beginning when one is writing the first plugins.

msj121 commented 1 year ago

There have been a few times where I have searched docs and had been pointed to somewhere else in docs. I feel like sometimes it can be tricky to find all the locations, even just a "look here for more" link where docs might overlap?

For example: I followed https://docs.vendure.io/typescript-api/core-plugins/payments-plugin/braintree-plugin/ and although it mentioned database update/sync this was something I thought happened automatically and not something I needed to configure. Pointing to here https://docs.vendure.io/developer-guide/migrations/#synchronize-vs-migrate would have cleared it up? Just food for thought.

michaelbromley commented 1 year ago

Ideas for guides layout:

michaelbromley commented 1 year ago

Other guides that need writing:

In the NestJS guide add a recipe for defining global providers like

import { VendurePlugin } from '@vendure/core';
import { APP_FILTER } from '@nestjs/core';

@VendurePlugin({
  providers: [
    {
      provide: APP_FILTER,
      useClass: MyExceptionFilter,
    },
  ],
})
export class MyPlugin {}
Caryntjen commented 1 year ago

What I struggled with was the migration (I generated duplicates, without running the migration and restarting the app), this of course is covered in other guides. But I think some best practices on migrations in a dedicated guide might help some newly developers.

michaelbromley commented 1 year ago

Preview of ongoing work: https://vendure-docs-beta.netlify.app/guides/getting-started/installation

tianyingchun commented 1 year ago

beautyfull UI interface!!!!

NoahPerez commented 1 year ago

If you can add in the marketplace docs, fundamentalmente parts needed to build a marketplace. I think it would would be the only one in the world open source fulling documented with cutting edge tech.

zehawki commented 1 year ago

Running list of some findings:

mschipperheyn commented 1 year ago

I have recently started working with Vendure. As impressed as I am with the toolbox, there are a few areas where I really went down rabbit holes as I started building a custom plugin that required fields from other custom plugins. I would suggest adding documentation on how to ensure you can access customFields and extensions on e.g. OrderLine or Customer inside a custom plugin resolver you are building, when those extensions were made by other plugins you are loading.

What I run into is that sometimes properties are loaded by services offered by those third party plugins or you are using the @vendure/core services. But in both cases, I'm struggling to get access to customFields created by other plugins that I know are there, but are not being loaded. Since you you are already "behind the resolver in the service layer", you can no longer rely on any kind of resolver based resolution to get those. Anyways, would love to get a howto on that.

michaelbromley commented 1 year ago

@mschipperheyn thanks, this is very good feedback and yes, not knowing which relations are joined is one of the main shortcomings of the TypeORM type system. Are you able to provide any small examples of things that are causing problems? E.g. what you tried that you expected to work but it didn't?

mschipperheyn commented 1 year ago

Hi @michaelbromley let me try to give you that example in a summary way

So, I'm building a plugin that uses two existing plugins, one is @pinelab customer-managed-groups. This plugin allows you to assign users that you consider as part of your group, e.g. family members. Another plugin we use allows you to assign that group member to an orderline, so you can buy products for your child.

import {
  Customer,
  LanguageCode,
  PluginCommonModule,
  VendurePlugin,
  Allow,
  Transaction,
} from "@vendure/core"
import gql from "graphql-tag"
import { Args, Mutation, Resolver } from "@nestjs/graphql"
import { Ctx, RequestContext, OrderService } from "@vendure/core"
import { Permission, OrderLine } from "@vendure/common/lib/generated-types"
import { isFunctionTypeNode } from "typescript"

const assignCustomerSchemaExtension = gql`
  extend type Mutation {
    assignCustomerToOrderLine(orderLineId: ID!, customerId: ID!): OrderLine!
  }
`

@Resolver("OrderLine")
export class OrderLineAssignmentResolver {
  constructor(private orderService: OrderService) {
    console.log("OrderLineAssignmentResolver constructor called")
  }

  @Transaction()
  @Mutation()
  @Allow(Permission.UpdateCatalog)
  async assignCustomerToOrderLine(
    @Ctx() ctx: RequestContext,
    @Args() args: any
  ) {
    const order = await this.orderService.findOneByOrderLineId(
      ctx,
      args.orderLineId
    )
    const orderId = order!.id
    console.log("assignCustomerToOrderLine called with args: ", args)
    // error checking
    return this.orderService.adjustOrderLine(
      ctx,
      orderId,
      args.orderLineId,
      1,
      { assignedCustomer: { id: args.customerId } }
    )
  }
}

@VendurePlugin({
  imports: [PluginCommonModule],
  shopApiExtensions: {
    schema: assignCustomerSchemaExtension,
    resolvers: [OrderLineAssignmentResolver],
  },
  configuration: (config) => {
    config.customFields.OrderLine.push({
      name: "assignedCustomer",
      type: "relation",
      entity: Customer,
      eager: false,
      defaultValue: false,
      nullable: true,
      label: [{ languageCode: LanguageCode.en, value: "Assigned Member" }],
    })
    return config
  },
  compatibility: "^2.0.0",
})
export class OrderLineAssignmentPlugin {}

One feature is that the plugin adds a customField to an OrderLine.

So, on my side, I'm building a plugin that ensures that you can't select any products that were already bought for user X or family member Y. So, I use pinelabs order retrieval for family members and try to verify that assignedCustomer. It doesn't exist on that (I guess retrieval is not using *) and also when I use the @vendure/core order manager, I cannot access the assignedCustomer on the retrieved orderLine.

So, I'm looking for how to get that assignedCustomer loaded up and have it recognized as a valid typescript property. That last part I think I'll be able to get through. The first part is trickier.

HTH

michaelbromley commented 1 year ago

Thanks for this detailed explanation, it is helpful!

So regarding the TS typings for custom fields, this is covered here: https://beta-docs.vendure.io/guides/developer-guide/custom-fields/#typescript-typings - let me know if that is helpful on that point.

Regarding the broader issues brought up, I think we need 2 sections added to the docs:

1. Working with relations

This section could be a sub-heading of this section which explains how relations work, i.e.

const order = await this.orderService.findOne(ctx, id, ['lines.customFields.assignedCustomer']);

2. Accessing custom fields via TypeScript code

A new section in the custom fields guide which demonstrates patterns for accessing custom fields, e.g.

const orderLine = await this.connection.getRepository(ctx, OrderLine).findOne({
  where: { id },
  relations: {
    customFields: { 
      assignedCustomer: true,
    }
  }
})

or with EntityHydrator:

const order = await this.orderService.findOne(ctx, id);
await this.entityHydrator.hydrate(ctx, order, { relations: ['lines.customFields.assignedCustomer'] });
michaelbromley commented 1 year ago

One to add the the Admin UI customization docs:

Add IDE plugin config example:

name: Vendure GraphQL Schema
schema: schema.graphql
extensions:
  endpoints:
    admin:
      url: http://localhost:3000/admin-api
    shop:
      url: http://localhost:3000/shop-api

Add section on mocking services in e2e tests: https://discord.com/channels/1100672177260478564/1147481709206568981/1148135234349584496

michaelbromley commented 1 year ago

Add section on all aspects of email handling, including how to toggle dev/prod mode in the email config:

  EmailPlugin.init({
        handlers: [
            // ...
        ],
        templatePath: path.join(rootDir, 'static/email/templates'),
        globalTemplateVars: {
            // ...
        },
        ...(DEV_MODE
            ? {
                  route: 'mailbox',
                  devMode: true,
                  outputPath: path.join(rootDir, 'static/email/output'),
              }
            : {
                  transport: {
                      type: 'smtp',
                      host: process.env.SMTP_HOST,
                      port: +process.env.SMTP_PORT,
                      //...
                  },
              }),
mschipperheyn commented 1 year ago

Another suggestion I have is to add a section to Error Handling that explains how to create your own Errors, in particular how to ensure that say a validation error gets converted to an ErrorResult if you use an ErrorResult union. My first impression is that it's prob not desirable to extend on Vendure's error result. I'm not even sure if it's possible to extend that ErrorCode enum. So, it's prob more of a nestjs style question but it's a scenario that's likely common if you're building your own plugins.

tianyingchun commented 1 year ago

yes, it should be expose on docs :)

// extends graphql `ErrorCode`
  extend enum ErrorCode {
    CUSTOM_ERROR_CODE
  }
  type CustomPluginError implements ErrorResult {
    errorCode: ErrorCode!
    message: String!
  }
// Create custom error class?
export class CustomPluginError {
  // eslint-disable-next-line @typescript-eslint/naming-convention
  readonly __typename = 'CustomPluginError';

  constructor(
    private readonly message: string,
    private readonly errorCode: string
  ) {
    console.log('runhere');
  }
}
// Throw the error?
return new CustomPluginError(
    `Some error  ocurr here?`,
    ErrorCode.CUSTOM_ERROR_CODE
);
mschipperheyn commented 1 year ago

From what I can tell from the issues (https://github.com/vendure-ecommerce/vendure/issues/437) and the repo there is actually a plugin for this

However, on my side, I do see the enum ErrorCode values being auto generated but not the error class being auto generated. I'm not so familiar with codegen and I get how it's supposed to work through graphql-errors-plugin but all of this is a bit auto-magical and hard to debug.

mschipperheyn commented 1 year ago

I finally got it. I have been dumb, but it is an easy one to trip over if you don't use you brain. You have use the isGraphQlErrorResult (or some other method) to determine if the error that is occurring in your resolver method is a an ErrorResult. In that case, you have to return it in stead of throwing it.

mschipperheyn commented 1 year ago

Another suggestion is documentation on how to handle T_id style ids in testing. Bc they are leading to not found results for me. Ids are stored as numbers 1,2,3 in our test database, but returned through the graphql database as T_1, T_2, T-3 through the graphql api. I'm guessing there must be a standard way of dealing with them on the service side. I mean I can write a utility method, but my guess is I'm doing something wrong. Reviewing the entity id strategy, that must be it

mschipperheyn commented 11 months ago

Documentation improvements: Add fields to existing types

Some aspects on adding custom fields to existing domain objects are not clear in the documentation: Context: I want to add an avatar to the Administrator.

  1. If you add a custom field to an existing entity, that it gets added to the graphql api automatically (If you try to do it manually you run into a dead end because custom field type definitions are created in the code generation step)
  2. How to add a custom field to the root of an existing entity instead of to the customFields object. The ProductVariant example makes clear that it's possible but not how to configure it at the entity level, since the documented way adds fields at the customFields level.
  3. How to avoid the Error: "Vendure Entity you are extending" defined in resolvers, but not in schema.The custom fields get generated automatically, but I need an entity resolver to retrieve the Asset that is added as the avatar field to customFields.

I added the avatar field as a custom field. Then to avoid ad 3, I tried to add something like

extend type AdministratorCustomFields {
   avatar: Asset
}

extend type Administrator {
    customFields: AdministratorCustomFields
}

But since AdministratorCustomFields didn't exist yet, I ran into ad 1.

extend type Administrator {
    customFields: {
        avatar: Asset
   }
}

is illegal