Skip to content

Commit

Permalink
feat: update configuration doc with updates from component PR
Browse files Browse the repository at this point in the history
  • Loading branch information
sdo-1A committed Apr 17, 2024
1 parent 92f7654 commit a3dca3d
Show file tree
Hide file tree
Showing 2 changed files with 354 additions and 414 deletions.
344 changes: 35 additions & 309 deletions docs/configuration/CONFIGURATION_SUPPORTED_EXTRACTOR.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,32 @@
# Configuration

The _component extractor_ is used to extract the configuration metadata from an application or a library. It extracts the list of all configuration interfaces defined in
a library or an application. The output is a JSON file `component.config.metadata.json` containing an array of all the component configurations (app configs, pages, components).
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 `component.config.metadata.json` containing an array of all the component configurations (app configurations, pages, components).
It also generates the metadata for component classes and types from an Otter library/application, outputting them in a JSON file `component.class.metadata.json`.

Note that the `component.class.metadata.json` file is currently only used in the scope of the rules engine, but will be required in the future for the next steps of the Adobe SPA Editor.

## How to use it

You should use the component-extraction builder directly in your `angular.json` file.
The Component Extractor is accessible via an NgCLI builder: `@o3r/components:extractor`.
For an up-to-date documentation, run `ng help @o3r/components:extractor`.
The Component Extractor is accessible via this NgCLI builder: `@o3r/components:extractor`.

First, define your filenames for the configuration in the `package.json` of the library/app where you run the extractor.
* When the extractor is run in a library, it uses this configuration to name the metadata files.
* When the extractor is run in an application, it searches for these file names in each node_module (in the `package.json` file) of each library,
in order to concatenate the file's metadata with the metadata of the other libraries and the application's metadata.
First, define your filenames for the configuration in the `package.json` of the library/application where you run the extractor.
When the extractor is run in a library, it uses this configuration to name the metadata files.
When the extractor is run in an application, it searches for these file names in each node_module (in the `package.json` file) of each library
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
// in package.json file
...
"cmsMetadata": {
...
"componentFilePath": "./component.class.metadata.json",
"configurationFilePath": "./component.config.metadata.json",
}
...
"cmsMetadata": {
"componentFilePath": "./component.class.metadata.json",
"configurationFilePath": "./component.config.metadata.json"
}
```

If the _component extractor_ is run on an application that is using components from an otter library, the _libraries_ option can be
specified to concatenate the classes/configuration files generated for the application with those of the specified _libraries_.
The extractor will search for a `component.config.metadata.json` file and a `component.class.metadata.json` file in the node_modules package
of each defined library (the name of the files to be searched for will be read from the `cmsMetadata` property in the `package.json` file defined for each library).

Here is an example on the otter demo app:
For an up-to-date documentation, run `ng help @o3r/components:extractor`

* 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 node_modules package 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
// in angular.json
"extract-components": {
"builder": "@o3r/components:extractor",
"options": {
Expand All @@ -45,310 +35,46 @@ Here is an example on the otter demo app:
"@your/o3r-components"
]
}
},
}
```

> [!NOTE]
> These 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 configuration. You can set it to `false` to enable the generation of metadata including the unknown types to
facilitate troubleshooting, and with errors logged as warnings.
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-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
}
"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 library in a mono repository, it's important to specify both the `configOutputFile` option and the `componentOutputFile` option.
By default, if not specified, the output files 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
{
{
"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 the configuration in the dedicated file of the component (`*.config.ts`).
The configuration should extend the interface of the configuration that is supported by the CMS.

It can also contain nested configuration which extends `NestedConfiguration`.
This interface is part of the @o3r/core package, with only primitive types allowed inside (string | boolean | number).
Optional types that are __not__ supported will be ignored by the extractor.

Here is an example of a configuration file containing all the supported types:

```typescript

/**
* a 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
}
]
};
```

Note that the order of the Nested configuration interface doesn't matter, but the default configuration needs to be placed __AFTER__ the
interface declaration, and must not contain any 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

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](https://github.com/AmadeusITGroup/otter/blob/main/docs/cms-adapters/CMS_ADAPTERS.md))
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 to provide a user-friendly name and 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, they will be ignored by the extractor. For instance, in the following example, `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

We have strong validators on the metadata output so if you face any issues when importing in the CMS, please
ensure that no warnings have been raised when running the cms-adapter.

You can also check the configuration causing the issue in your metadata file to see if there is anything unusual.

0 comments on commit a3dca3d

Please sign in to comment.