Skip to content

Commit

Permalink
feat: update configuration doc
Browse files Browse the repository at this point in the history
  • Loading branch information
sdo-1A committed May 16, 2024
1 parent bbae039 commit fba5b33
Show file tree
Hide file tree
Showing 3 changed files with 436 additions and 524 deletions.
346 changes: 43 additions & 303 deletions docs/configuration/CONFIGURATION_SUPPORTED_EXTRACTOR.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
# Configuration
To extract the configuration metadata from an application/library the _component extractor_ will be used. It extracts the list of all the config interfaces defined in a library or application. The output is a json file `component.config.metadata.json` containing an array of all the components configurations (app configs, pages, components).
It will also generate the component classes and types metadata from an Otter library/application, outputting them in a json file `component.class.metadata.json`.
The component extractor is used to extract the configuration metadata from an application or a library. It extracts the list of all the config interfaces defined
in a library or an application. The output is a JSON file (named `component.config.metadata.json` by default) containing an array of all the component configurations.
It also generates the metadata for component classes and types from an Otter library/application, outputting them in a JSON file (named `component.class.metadata.json` by default).

Note that the component.class.metadata.json is used at the moment only in rules engine scope, but in the future will be a requirement for the next steps of the Adobe SPA Editor.
## How to use it

Since the v3 of Otter, you should use the component-extraction builder directly in your angular.json
The Component Extractor is accessible via this [Angular CLI builder](https://angular.io/guide/cli-builder): `@o3r/components:extractor`.

First thing to do is to define your given filenames for the configuration in the _package.json_ of the library/app where you run the extractor.
When running in a library it will use this configuration as the names for the metadata files.
When running the extractor in an application, it will search for these filenames in each node_module (package.json file) of each library,
in order to concat the metadata from the file with other libraries metadata and app metadata.
First, define the file names of the package metadata in the `package.json` of the library/application where you run the extractor.
When the extractor is run in a project, it will use these file names to name the output files of the extraction. In case you want to compile the
metadata of the project with its dependencies (generally for an application that has libraries with their own metadata as well), the extractor will need to find the metadata for each dependency.
In that case, the extractor will search for these file names in the `package.json` file of each library (in the `node_modules`)
in order to concatenate the file's metadata with the metadata of other libraries and the application's metadata.

In the `package.json` of the library:
```json
"cmsMetadata": {
"componentFilePath": "./component.class.metadata.json",
"configurationFilePath": "./component.config.metadata.json"
}
```
// in package.json file
...
"cmsMetadata": {
...
"componentFilePath": "./component.class.metadata.json",
"configurationFilePath": "./component.config.metadata.json",
}
...
```

The Component Extractor is accessible via a NgCLI builder: `@o3r/components:extractor`;

For an up-to-date documentation, run `ng help @o3r/components:extractor`

* If the _component extractor_ is run on an application which is using components from an otter library, the _libraries_ option can be specified to concat classes/configuration files generated for application with ones from specified _libraries_. The extractor will search a _'component.config.metadata.json'_ and a _component.class.metadata.json_ file in the node_modules package of each defined library (the name of the files to search will be read from the _cmsMetadata_ property in _package.json_ file defined for each library).
* Here is an example on the otter demo app

```
// in angular.json
* If the __component extractor__ is run on an application with components from an Otter library, the `libraries` option can be used to concatenate the metadata files generated for the application with the ones from the specified libraries.
The extractor will search for the component configuration and style metadata files in the dependencies of each configured library.
The name of the metadata files to search for is defined for each library in the `cmsMetadata` property defined in their respective `package.json` files.
* Here is an example of an `angular.json` file:
```json
"extract-components": {
"builder": "@o3r/components:extractor",
"options": {
Expand All @@ -40,306 +36,50 @@ For an up-to-date documentation, run `ng help @o3r/components:extractor`
"@your/o3r-components"
]
}
},
}
```

__Note:__ This options will not search for the duplicate configurations in libraries.
> [!WARNING]
> This option will not search for the duplicate configurations in libraries.
The __strict mode__ option should be used for production builds, it will throw an error if something not supported by the
CMS is found in the config. You can set it to `false` to allow the generation of metadata including the unknown types to
ease issues fixing, and with the errors logged as warnings.

Here is an example on a library
The `strictMode` option should be used for production builds, it will throw an error if an inconsistency or error is found in the configuration.
You can set it to `false` to enable the generation of metadata including the unknown types to facilitate troubleshooting, and with the errors logged as warnings.

Here is an example on a library:
```json
{
//...
"extract-configurations": {
"builder": "@o3r/components:extractor",
"options": {
"tsConfig": "modules/@scope/area/components/tsconfig.metadata.json",
"configOutputFile": "modules/@scope/area/components/dist/component.config.metadata.json",
"componentOutputFile": "modules/@scope/area/components/dist/component.class.metadata.json",
"strictMode": true
}
"extract-components": {
"builder": "@o3r/components:extractor",
"options": {
"tsConfig": "modules/@scope/area/components/tsconfig.metadata.json",
"configOutputFile": "modules/@scope/area/components/dist/component.config.metadata.json",
"componentOutputFile": "modules/@scope/area/components/dist/component.class.metadata.json",
"strictMode": true
}
//...
}
```

In case you have a lib in a mono repository, it's important to specify both configOutputFile and componentOutputFile, by
default if not specified, it will be generated at the root of the project
In case you have a library in a mono repository, it's important to specify both the `configOutputFile` option and the `componentOutputFile` option.
It will prevent the files generated for the library to conflict with the ones generated for the application because by default, if it is not specified, the output files will be generated at the root of the project.

For the tsconfig, you should include only the files that you want to parse, i.e. components, modules and config. You can
extend your tsconfig, and just override the fields that you need.
For the `tsconfig`, you should include only the files that you want to parse, i.e. components and config. You can
extend your `tsconfig`, and just override the fields that you need.

Example of a tsconfig :
Example of a `tsconfig` :

```json
{
```json5
{
"extends": "./tsconfig",
"rootDir": ".",
"include": [
"src/**/*.component.ts",
"src/**/*.module.ts",
"src/**/*.config.ts"
],
"exclude": [
"Put all paths that you would like to exclude here ex : src/**/*.spec.ts"
]
}
```

## Component file (*.component.ts)

Here is an example of configuration in the component, note that dynamicConfig$ is there for the configuration service
and config$ for the configuration store

```typescript
// ...
export class MyComponent implements DynamicConfigurable<MyConfig> {
@Input()
public config?: Partial<MyConfig>;

/** Dynamic configuration based on the input override configuration and the configuration service if used by the application */
private dynamicConfig$: ConfigurationObserver<MyConfig>;

/** Configuration stream based on the input and the stored configuration */
public config$: Observable<MyConfig>;

//...

constructor(
// ...
@Optional() configurationService?: ConfigurationBaseService
) {
this.dynamicConfig$ = new ConfigurationObserver<MyConfig>(MY_CONFIG_ID, MY_DEFAULT_CONFIG, configurationService);
this.config$ = this.dynamicConfig$.asObservable();
}

}
```

## Configuration file (*.config.ts)

You need to implement Configuration

NestedConfiguration is part of @o3r/core package, with only primitive types allowed inside ( string | boolean |
number)

OPTIONAL Types NOT supported: they will be ignored by the extractor Here is an example of configuration file containing
all the supported types :

```typescript

/**
* an UnionType with string values used in configuration (ex: can be reused for several fields)
*/
type Position = 'top' | 'bottom';

/**
* MyConfig description
*/
export interface MyConfig extends Configuration {
/**
* myStringField description
*/
myStringField: string;

/**
* myBooleanField description
*/
myBooleanField: boolean;

/**
* myNumberField description
*/
myNumberField: number;

/**
* myStringListField description
*/
myStringListField: string[];

/**
* myUnionTypeField description
*/
myUnionTypeField: 'before' | 'after';

/**
* myReferencedUnionTypeField description
*/
myReferencedUnionTypeField: Position;

/**
* myNestedField description
*/
myNestedField: MyNestedConfig[];
}

/**
* MyNestedConfig description
*/
interface MyNestedConfig extends NestedConfiguration {
/**
* myNestedStringField description
*/
myNestedStringField: string;

/**
* myNestedBooleanField description
*/
myNestedBooleanField: boolean;

/**
* myNestedNumberField description
*/
myNestedNumberField: number;
}

export const MY_DEFAULT_CONFIG: MyConfig = {
myStringField: 'myStringField default value',
myBooleanField: false,
myNumberField: 0,
myUnionTypeField: 'before',
myReferencedUnionTypeField: 'top',
myStringListField: ['firstDefaultValue', 'secondDefaultValue'],
myNestedField: [
{
'myNestedStringField': 'myNestedStringField default value 1',
'myNestedBooleanField': false,
'myNestedNumberField': 15
},
{
'myNestedStringField': 'myNestedStringField default value 2',
'myNestedBooleanField': true,
'myNestedNumberField': 10
}
// Put all paths that you would like to exclude here (ex: src/**/*.spec.ts)
]
};
```

Note that the order for the Nested config interface doesn't matter, but the default config needs to be put AFTER the
interface declaration, and contain no variable references.

`UnionTypes` are supported in 2 cases:

1) inline definition (see above `myUnionTypeField`)
2) reference to a union type that is defined in the same configuration file (see above `myReferencedUnionTypeField` and `Position`).

### Configuration tags

Tagging configuration is possible starting otter v4.1. To implement this feature, one should add the tags in the JSDoc
of the configuration interface while respecting the following format:

```typescript
/**
* MyConfig description
* @tags [tag1, tag2,
* tag3]
*
*/
export interface MyConfig extends Configuration {

}
```

These tags will be exported inside the extracted metadata (see the [CMS Adapters documentation](../cms-adapters)) provided
they are supported in the [CMS JSON schema](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40o3r/configuration/schemas/configuration.metadata.schema.json). Please refer to the schema for the latest supported model.

For instance, if you want to add a title to your component's configuration as a way to have a user friendly naming and a label for your property, you can set the following tags:
```typescript
/**
* This is an incredible config but the name is not so easy to read for CMS users
*
* @title My Incredible Config
*/
export interface MyConfigWithADifficultName extends Configuration {
/**
* My great property
*
* @label Human readable title
*/
myConfigProperty: string;
}
```

If you use any non supported tags in your tsdocs, it will be ignored by the extractor. For example, in the following example, the invalidTag will not be part of the
extracted metadata.
```typescript
/**
* Yet another Configuration
*
* @invalidTag This tag will be ignored by the configuration extractor
*/
export interface MyConfig extends Configuration {
/**
* Some description
*/
someConfigProperty: string;
}
```

### Configuration categories

Categories can be added on configuration properties. This can be achieved by adding the `@o3rCategory` tag in the JSDoc on the configuration property.
The categories added on the configuration properties must be defined either globally or in the configuration interface.

For the first case, the global categories can be defined in the `angular.json` of your project by adding the `globalConfigCategories` property to the options of `@o3r/components:extractor`, for example:

```json5
// in angular.json
"extract-components": {
"builder": "@o3r/components:extractor",
"options": {
//...
"globalConfigCategories": [
{ "name": "globalCategory", "label": "Global category" }
]
}
}
```


For the second case, the categories can be described using the `@o3rCategories` tag in the JSDoc on the configuration interface.
Their syntax is the tag `@o3rCategories` followed by the category name and an optional label (if the label is not provided, it will be assigned
the value of the category name with the first letter capitalized, for example `@o3rCategories categoryName` is equivalent to `@o3rCategories categoryName CategoryName`).

Example:

```typescript
/**
* Show the motto on the right of the screen
*
* @tags [one, two, three]
*
* @o3rCategories presentation configuration linked to display
* @o3rCategories localization configuration related to languages and translations
*/
export interface SimpleHeaderPresConfig extends Configuration {
/**
* Show the motto on the right of the screen
* @o3rCategory presentation
*/
showMotto: boolean;

/**
* Show language selection dropdown (localization)
* @o3rCategory localization
*/
showLanguageSelector: boolean;

/**
* Propose round trip
* @o3rCategory globalCategory
*/
shouldProposeRoundTrip: boolean;
}
```

## Troubleshooting

Starting from Otter 3.5 we have strong validators on the metadata output, if you face any issue importing in the CMS,
ensure that you have no warning raised in the cms-adapter run.
# Package link

You can also check the config causing the issue in your metadata file to see if there is something unusual.
Find the `@o3r/configuration` package [here](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/configuration/README.md).

0 comments on commit fba5b33

Please sign in to comment.