From f58ea72ffce4bf586397620e87eada3bfd2245eb Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 11 Oct 2024 13:12:30 +0100 Subject: [PATCH] chore(docs): some updates to our engineering handbook Signed-off-by: John Cowen --- docs/1-tldr.md | 74 +++++-- docs/2-engineering-utilities.md | 11 +- docs/4-modules.md | 62 ++++-- docs/5-routing.md | 58 ++++-- .../application/components/app-view/README.md | 43 ++-- .../components/route-view/README.md | 194 +++++++++++++----- 6 files changed, 328 insertions(+), 114 deletions(-) diff --git a/docs/1-tldr.md b/docs/1-tldr.md index 6abebcc28..431523b2d 100644 --- a/docs/1-tldr.md +++ b/docs/1-tldr.md @@ -34,20 +34,20 @@ Routing configuration is stored in a modules `routes.ts` file i.e. `@/app/module.name/routes.ts`. If your route is brand new add the JSON configuration in there. +:::danger All route views should use `RouteView`s and `AppView`s. Please make use of Vue's nested routing/`RouterView` when required. Remember you can pass data down through `RouterView`s. +::: -For more detail see: - -- [RouteView](/src/app/application/components/route-view/README) -- [AppView](/src/app/application/components/app-view/README) +TLDR: ```vue Page: {{ route.params.page }} - - + + ``` +For more detail see: + +- [RouteView](/src/app/application/components/route-view/README) +- [AppView](/src/app/application/components/app-view/README) + ## Working with data All read data interactions should use `DataSource` and or `DataLoader`. +:::warning `DataSource` is **non-blocking** i.e. its contents will show always whether the data is loaded or not. `DataLoader` is **blocking** and can be used with or without `DataSource` depending on which parts of the UI you want to be blocked. `DataLoader` can also load multiple `DataSource`s. +::: We often use `DataCollection` for filtering/finding data inline and providing empty states. -For more detail see: +:::warning +`DataSource` has no visual elements/states. -- [DataSource](/src/app/application/components/data-source/README) -- [DataLoader](/src/app/application/components/data-source/README#simple-dataloader-usage) -- [DataCollection](src/app/application/components/data-collection/README) +`DataLoader` has automatic (but slottable) loading and error states. + +`DataCollection` has an automatic (but slottable) empty state. +::: + +As `DataLoader` has visual states for loading and error states, we generally +only use `DataSource` for more low level data-requesting and/or loading from +multiple sources at once and/or more complex data requesting sequences. + +TLDR: ```vue - - {{ data?.items.length }} + + Always show me (guard for data is required as it might be undefined) + {{ data?.items.length }} +... + + Only show me once the data is loaded + {{ data.items.length }} + + + We are guaranteed to have at least one item, + otherwise the default empty state is shown. + {{ data.items[0].name }} + + ``` +You could also use our `uri` helper for creating URIs/URLs for the `src` +parameter to enable auto-completion of URIs and type hints for the outputted +data. + +For more detail see: + +- [DataSource](/src/app/application/components/data-source/README) +- [DataLoader](/src/app/application/components/data-source/README#simple-dataloader-usage) +- [DataCollection](/src/app/application/components/data-collection/README) + + ## GUI Components Apart from our `Data*` components we also have a set of `X*` components which diff --git a/docs/2-engineering-utilities.md b/docs/2-engineering-utilities.md index 9b1c7c4c4..b1428551c 100644 --- a/docs/2-engineering-utilities.md +++ b/docs/2-engineering-utilities.md @@ -42,11 +42,20 @@ make your own and if any become really common feel free to add here. | ---- | ----- | | * Create Scenario | Creates a new bookmarklet and link from your current cookies for dragging to your bookmark bar | | --- | --- | +| Disable Mocking| Turn off HTTP API mocking. HTTP requests will be sent to a locally running kuma installation at http://localhost:5681 | | Environment: kubernetes| Set the `KUMA_ENVIRONMENT` to `kubernetes` | | Environment: kubernetes| Set the `KUMA_ENVIRONMENT` to `universal` | -| Disable Mocking| Turn off HTTP API mocking. HTTP requests will be sent to a locally running kuma installation at http://localhost:5681 | | Disable Zones| Set the `KUMA_MODE` to `standalone` i.e. disable `zones` and only use a non-federated kuma with only Zone Egress | | Enable Zones| Set the `KUMA_MODE` to `global` i.e. enable `zones` and use a federated kuma with Zone support | +| Debug i18n strings| Show i18n keys instead of strings | +| Latency: 3000| Set the HTTP API latency (defaults to 3000ms) | + +::: tip +There are very many `KUMA__COUNT` variables to allow you to +control the amount of data returned by the HTTP API. Using for example +`KUMA_MESH_COUNT=0` is useful for viewing empty states. +::: + ## `debug` service containers diff --git a/docs/4-modules.md b/docs/4-modules.md index f728f516b..16ae5c8de 100644 --- a/docs/4-modules.md +++ b/docs/4-modules.md @@ -3,19 +3,36 @@ section: Overview --- # Modules -In `src/app/$module/`. +Our modules/packages are currently in `src/app/$module/`, but will eventually +be moved to fully fledged npm modules via workspaces. -A big part of the project’s business logic is loosely grouped into modules. For example, there is a zones module in `src/app/zones/`, a services module in `src/app/services/`, etc. A module consists of components and scripts for utility functions, route definitions, etc. +The application is grouped into separate modules/packages, mostly by "Kuma +Noun". For example, there is a zones module in `src/app/zones/`, a services +module in `src/app/services/`, etc. + +There are other more generic/framework modules such as `application`, `msw`, +`me`, `x` etc. + +A module consists of several things such as: + +- `index`: service container configuration +- `routes`: Vue routing configuration +- `sources`: definitions for URIs/DataSources i.e. data retrieval +- `features`: user 'abilities' definitions, i.e. feature flags +- `components/`: components specific to this package +- `views/`: views/pages/routes specific to this package +- `data/`: data manipulation/correction/reshaping +- `locales/` i18n strings ## Components -In `src/app/$module/components/` or `src/app/$module/views/`. +The application has two distinct types of "component". "View", "Route" or +"Page" components and re-usable components. -A module’s components are placed in either a `components` or a `views` directory. Views are components that are hooked up to route definitions and are placed in the `views` directory. Any other component is placed in the `components` directory. +Route components are assigned to each specific route within `route.ts`. These +components currently live in a flat structure in `src/app/$module/views/`. -::: tip NOTE -Components that aren’t module-specific (or shared by multiple modules) are placed in `src/app/common/` instead. -::: +Other re-usable components live in `src/app/$module/components/` ## Locales @@ -25,13 +42,10 @@ In `src/app/$module/locales/$locale/`. Learn more about [i18n](/src/app/application/services/i18n/README.md) ::: -A module typically has message files which contain the visible/human-readable UI text in the form of a YAML object. Each piece of text is associated to a key in the form of a path that represents the location of the message in the YAML object. - -## Utilities - -In `src/app/$module/utilities/`. - -Holds module-specific utility functions. +A module typically has message files which contain the visible/human-readable +UI text in the form of a YAML object. Each piece of text is associated to a key +in the form of a path that represents the location of the message in the YAML +object. ## Features @@ -41,7 +55,10 @@ Learn more about [can](/src/app/application/services/can/README.md) In `src/app/$module/features.ts`. -Holds a module’s feature definitions. Features are used to lock down or expose parts of the application based on some runtime state using the [can](/src/app/application/services/can/README.md) utility. Features are _defined_ in a module but can be used outside of modules. +Holds a module’s feature definitions. Features are used to lock down or expose +parts of the application based on some runtime state using the +[can](/src/app/application/services/can/README.md) utility. Features are +_defined_ in a module but can be used outside of modules. ## Routes @@ -51,7 +68,8 @@ In `src/app/$module/routes.ts`. Learn more about [routing](/docs/routing.md) ::: -Holds a module’s route definitions. They’re used to set-up the application routes. +Holds a module’s route definitions. They’re used to set-up the application +routes. ## Sources @@ -61,7 +79,12 @@ In `src/app/$module/sources.ts`. Learn more about [DataSource](/src/app/application/services/data-source/README.md) ::: -Holds a module’s data source definitions. They’re used to provide read access to data (most commonly via our HTTP API). +Holds a module’s data source definitions. They’re used to provide read access +to data (most commonly via our HTTP API). + +## Data + +In `src/app/$module/data/` ## Service definition @@ -71,5 +94,8 @@ In `src/app/$module/index.ts`. Learn more about [services](/src/services/README.md) ::: -Defines the module so it can be exposed to our dependency injection layer. This makes it so that a module’s features and sources are available throughout the application. +Defines the module so it can be exposed to our dependency injection layer. This +makes it so that a module’s features and sources are available throughout the +application in a way that can be configured, grouped, decorated and overridden +from The Outside i.e. without altering the code of the application itself. diff --git a/docs/5-routing.md b/docs/5-routing.md index 5978da0d7..72af1b5f9 100644 --- a/docs/5-routing.md +++ b/docs/5-routing.md @@ -3,21 +3,28 @@ section: Overview --- # Routing -In out application, documents (or views) are associated with URL paths using [vue-router](https://router.vuejs.org/). Every view has its own URL. Most views are representations of a resource (e.g. `/meshes` is a list of meshes, `/meshes/default` is a detail of the default mesh, `/meshes/default/services` is a list of services in the mesh “default”, etc.). +In the application, documents (or views) are associated with URL paths using +[vue-router](https://router.vuejs.org/). Every view has its own URL. Most views +are representations of a resource (e.g. `/meshes` is a list of meshes, +`/meshes/default` is a detail of the default mesh, `/meshes/default/services` +is a list of services in the mesh “default”, etc.). ## How to set-up module routes -::: tip -Learn more about [Modules](/docs/modules.md) -::: - -To create a consistent set of URLs for our application, we follow a couple of rules. One key objective is to make sure that views in their current state have robust URLs (i.e. I can send you the URL of the Data Plane Proxy list view filtered by name “demo” on page 2 and that same state is restored when you access that URL). +To create a consistent set of URLs for our application, we follow a couple of +rules. One key objective is to make sure that views in their current state have +robust URLs (i.e. I can send you the URL of the Data Plane Proxy list view +filtered by name “demo” on page 2 and that same state is restored when you +access that URL). ### Naming -Each route definition has a path segment. All path segments are lowercase and dash-delimited. Resource names are plural (if there can be multiple instances of that resource). +Each route definition has a path segment. All path segments are lowercase and +dash-delimited. Resource names are plural (if there can be multiple instances +of that resource). -An example of route paths and their associated views based on the rules laid out below: +An example of route paths and their associated views based on the rules laid +out below: | Path | View | | ------------------------ | ----------------------------- | @@ -31,10 +38,13 @@ An example of route paths and their associated views based on the rules laid out **Example**: `/meshes` -For a resource list, the path segment is the plural variant of the resource name (e.g. `Mesh` → `/meshes`). +For a resource list, the path segment is the plural variant of the resource +name (e.g. `Mesh` → `/meshes`). ::: tip NOTE -We do stretch this rule occasionally. For example, the service list view is populated using `ServiceInsight` objects and its path segment is `/services` (not `/service-insights`). +We do stretch this rule occasionally. For example, the service list view is +populated using `ServiceInsight` objects and its path segment is `/services` +(not `/service-insights`). ::: ### Create @@ -43,29 +53,39 @@ We do stretch this rule occasionally. For example, the service list view is popu For a create view, a single static path segment `/-create` is used. -::: tip WARNING -A create view route (e.g. `/meshes/-create`) **must** be defined before routes for list views with a selected resource (e.g. `/meshes/default`) to make sure the create view route is matched first. +::: warning +A create view route (e.g. `/meshes/-create`) **must** be defined before routes +for list views with a selected resource (e.g. `/meshes/default`) to make sure +the create view route is matched first. ::: -::: tip NOTE -The path segment is chosen to be an invalid DNS name (which can’t start with a `-` character). This ensures the path is unambiguous and doesn’t conflict with a resource of the same name (e.g. a resource called `create`). -::: +The path segment is chosen to be an invalid DNS name (which can’t start with a +`-` character). This ensures the path is unambiguous and doesn’t conflict with +a resource of the same name (e.g. a resource called `create`). ### List with selected resource **Example**: `/meshes/:mesh` -For a resource list that allows selecting an individual resource in order to display a kind of summary, a single dynamic path segment is used. This segment is dynamic and is either the unique name of a resource or the unique ID of a resource. +For a resource list that allows selecting an individual resource in order to +display a kind of summary, a single dynamic path segment is used. This segment +is dynamic and is either the unique name of a resource or the unique ID of a +resource. ### Detail **Example**: `/meshes/:mesh/overview` -For a resource detail, two path segments are used. Detail views **must** be defined as child routes of the corresponding list view unless the resource only exists once (and so a list view doesn’t exist for it in the first place). +For a resource detail, two path segments are used. Detail views **must** be +defined as child routes of the corresponding list view unless the resource only +exists once (and so a list view doesn’t exist for it in the first place). -The first path segment is dynamic and is either the unique name of a resource or the unique ID of a resource. +The first path segment is dynamic and is either the unique name of a resource +or the unique ID of a resource. -The second path segment is static and should describe the kind of resource detail as there can be multiple detail views for a single resource. It should be a noun. For the default/main detail view, we usually use `/overview`. +The second path segment is static and should describe the kind of resource +detail as there can be multiple detail views for a single resource. It should +be a noun. For the default/main detail view, we usually use `/overview`. ### Update diff --git a/src/app/application/components/app-view/README.md b/src/app/application/components/app-view/README.md index 855e21ec5..55fcc629c 100644 --- a/src/app/application/components/app-view/README.md +++ b/src/app/application/components/app-view/README.md @@ -3,13 +3,22 @@ type: component --- # AppView -Similar to `RouteView`, the `AppView` component should be used in every -routable `*View.vue` component. But it does not need to be used in a particular -position although its likely that it would be a fairly close child of the -`RouteView` component for the route: +Similar to `RouteView`, the `AppView` component should be used in **every +routable `*View.vue` component**. Think of it as your `` tag. + +It should go inside the `RouteView`, however it does not need to be the +immediate child, but it commonly is - or at least a fairly close child. + +The following will show no breadcrumbs at all, and render/announce the +`service` route parameter as the page title. ```vue - + @@ -23,9 +32,9 @@ position although its likely that it would be a fairly close child of the ``` -`AppView` mainly contains functionality to set the breadcrumbs for the -application, but also includes a slot for correctly positioning the -header/title for the page. +`AppView` is very application specific. It mainly contains functionality to set +the breadcrumbs for the application, but also includes a slot for correctly +positioning the header/title for the page. ::: warning Even if you do not need to set a breadcrumb please still wrap your route/page @@ -39,7 +48,7 @@ Only the topmost `AppView` (usually in `App.vue`) contains an instance of breadcrumbs to the top most `AppView` in order to be combined and rendered using `XBreadcrumbs`. -Therefore you only need to specify the breadcrumbs for your `*View.vue` +Therefore you only need to specify the breadcrumb(s) for your `*View.vue` route/view component, the breadcrumbs will automatically be prepended with the breadcrumbs from any parent components in a nested route structure. @@ -81,12 +90,22 @@ You can of course specify multiple breadcrumbs for a single route/view component ``` -If you need to tell the application not to show any breadcrumbs all (for example a child route that is fullscreen). Specify an empty set of breadcrumbs. +If you need to tell the application not to show any breadcrumbs all (for +example a child route that is fullscreen). Specify an empty set of breadcrumbs. + +```vue + +... + +``` -This is different to specify no breadcrumb property at all, specifying no property all all will just not append a new breadcrumb to the current set. +This is different to specifying no breadcrumb property at all, specifying no +property all will just not append a new breadcrumb to the current set. ```vue - + ... ``` diff --git a/src/app/application/components/route-view/README.md b/src/app/application/components/route-view/README.md index c30db03eb..d790f77e2 100644 --- a/src/app/application/components/route-view/README.md +++ b/src/app/application/components/route-view/README.md @@ -8,23 +8,107 @@ routable `*View.vue` component**. Think of it as your `` tag. ::: danger When using this you should specify the name of the route you are creating as -the RouteView's `name` property. +the RouteView's `name` property. See `name="route-name"` in the below example. ::: -`RouteView` contains functionality to: +`RouteView` contains functionality for: -- make it easy to set top-level DOM values that you usually wouldn't have - direct access to, for example [setting the HTML ``](#setting-the-title-of-the-page) or [the HTML `class` attribute](#setting-html-node-attributes). -- [provide access to utilities/tooling commonly used in route views](#exposing-commonly-used-utilities) i.e. - `route`, `t` and `uri` +- ["Better" `route.params`](#better-route-params) + - define and access reactive route params with a finer level of granularity + to using `vue-router`s `route` + - specify type and provide defaults for route params + - automatically transform boolean params to boolean query params. + - provide route params as a two-way binding +- setting top-level DOM values + - [the HTML `<title>`](#setting-the-title-of-the-page) + - [the HTML `class` attribute](#setting-root-node-attributes). +- [providing access to utilities/tooling commonly used in route views](#exposing-commonly-used-utilities) + such as `t`, `can`, `me` + +It generally looks something like this: + +```vue +<RouteView + name="route-name" + :params="{ + define: '' + route: '' + params: false + }" + v-slot="{ route, t, can, me }" +> +...View contents... +</RouteView> + +``` + +## "Better" route params + +::: danger +`route` is not an instance of `vue-router`'s `route`. It is very similar but +omits some things and adds functionality to others. +::: + +```vue +<RouteView + name="route-name" + :params="{ + mesh: '' + service: '' + page: 1 + checked: false + }" + v-slot="{ route }" +> + Page: {{ route.params.page }} + ... +</RouteView> +``` + +The above example illustrates how to define the dependencies of your route +template/view, also known as the route params. If you do not need a specific +route param in a template you should not define them in this specific +template/RouteView. + +::: warning +Note: at this level there is no concept of "path vs query" params. They are +just all "params". Whether they end up being path or query params is controlled +by your routing config. If you refer to the param in a URL in your route +config, they end up being path params, if not, they are query params. +::: + + +Defining these "dependencies" here has several advantages: + +1. Accessing `route.params` gives you autocompletion/type-checking in your IDE +2. The reactivity is finer grained. When using `vue-router`s `route.params`, + `params` itself if the thing that is reactive, i.e. the entire object. If + *any* route param changes, whether you use it in your template or not, + accessing `vue-router`s `route.params` in your template will force a re-render. + On the other hand, if you use `RouteView::params` only when a route param we + have defined as a dependency changes forces a re-render. +3. Provide default values for the param. In the above example if `page` param + isn't set in the route, then a query param of `?page=1` will be to the URL + by default and `route.params.page` will equal `1`. +4. Boolean route params are transformed correctly for the URL. In the above + example the resulting query parameter will be the existence of `?checked` + not `?checked=true` and `?checked=false` +5. Route params can be used as `v-model` values. Ideally we usually use events + to change route properties, but for components that only support `v-model` + you can use the route.param as the v-model. For example `<KCheckBox + v-model="route.params.checked" />` will automatically sync the checkbox state + to the `?checked` query parameter. This aims to make it easier to maintain + state in the browser URL and harder not to ## Setting the `<title>` of the page `RouteView` works in tandem with `RouteTitle` allowing you to set the HTML title for -the page from anywhere in your `*View.vue` component. +the page in-place anywhere in your `*View.vue` component. ```vue -<RouteView name="route-name"> +<RouteView + name="route-name" +> <h1> <RouteTitle title="The title" @@ -35,11 +119,33 @@ the page from anywhere in your `*View.vue` component. ``` `RouteTitle` also renders the title you give it into the page, meaning that you -can easily use the same text for your page header and the HTML title. +can easily use the same text for your page header and the HTML title. To +disable this render you can use the `render` attribute: + +```vue +<RouteView + name="route-name" +> + <RouteTitle + title="The title" + :render="false" + /> + <h1> + Different H1 to the HTML title + </h1> +... +</RouteView> +``` Titles are automatically joined together as you would expect in a nested route structure and finally suffixed by the title of the product (taken from our i18n -strings). The above code, depending on nested routes could end up like: +strings). + +Additionally `RouteTitle` adds additional HTML markup at the root of the +application to announce the HTML title to a11y screenreaders. A11y +announcements are not joined. + +The above code, depending on nested routes could end up like: ```vue <html> @@ -47,13 +153,13 @@ strings). The above code, depending on nested routes could end up like: <title>The title | Parent title | Kuma Manager ... + +
Navigated to The title
+ ``` -Additionally `RouteTitle` adds additional HTML markup at the root of the -application to announce the HTML title to a11y screenreaders. - -## Setting HTML node attributes +## Setting root node attributes ::: warning Currently we only support setting the `className`. If you need to add further @@ -73,7 +179,7 @@ attributes such as `data-*`, you'll need to add that functionality to ``` Using the `attrs` attribute in the above code will result in the following -HTML: +root/html node: ```html @@ -85,53 +191,38 @@ The logic is careful not to remove/change any statically added attributes that existed on the node previously, and any attributes you add will be automatically removed when the `*View.vue` route/view component is removed from the page/DOM. +If you have 2 nested RouteViews adding the same class, navigating away/removing +only one of them will not remove the class. Only when there are no RouteViews +at all in the entire DOM tree with the class will the className be removed from +the root HTML node. + ## Exposing commonly used utilities `RouteView` exposes a selection of commonly used utilities from its default slot. ```vue ``` - `t`: The usual i18n `t` function i.e. `t('a.i18n.key')` - `env`: Access string based environment variables i.e. `env('KUMA_API_URL')` -- `can`: Make decisions based on a users abilities/enabled featureset i.e. `can('use meshservice')` +- `can`: Make decisions based on a users abilities/enabled feature-set i.e. `can('use meshservice')` - `uri`: Helper for creating type-safe URI strings specifically for use with [DataSource](/src/app/application/components/data-source/README) -- `route`: Access several route utilities `route.params`, `route,children`, `route.update()` etc. +- `route`: Access several route utilities `route.params`, `route.children`, `route.from`, `route.update()` etc. +- `me`: Accessing and writing user profile information -::: warning -`route` is not an instance of `vue-router`'s `route`. It is very similar but -removes access to some things and dds functionality to others. -::: - -```vue - -

- - Page: {{ route.params.page }} -

-... -
-``` ## Props -| Name | Description | -| ------ | ------------------------------------------------------------------------------------------------------- | -| `name` | The name of the current route file as specified in your routing configuration i.e. `services-list-view` | +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------- | +| `name` | The name of the current route file as specified in your routing configuration i.e. `services-list-view` | +| `params` | The route param dependencies of this Route | +| `attrs` | Sets root node HTML attributes | ## Slots @@ -139,11 +230,12 @@ removes access to some things and dds functionality to others. #### exports -| Name | Description | -| ------- | --------------------------------------------------------------------------- | -| `route` | an object with route based utilties (this, is **not** Vue route, see above) | -| `t` | A reference to our `t` function/service | -| `env` | A reference to our `env` function/service | -| `can` | A reference to our `can` function/service | -| `uri` | A reference to our `uri` helper | +| Name | Description | +| ------- | --------------------------------------------------------------------------- | +| `route` | an object with route based utilities (this, is **not** Vue route, see above) | +| `t` | A reference to our `t` function/service | +| `env` | A reference to our `env` function/service | +| `can` | A reference to our `can` function/service | +| `uri` | A reference to our `uri` helper | +| `me` | Access to the user profile |