From f0470537a0685191df8f9fa1d3c71a9dc4ffe3e3 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Tue, 1 Nov 2022 23:39:20 +0100 Subject: [PATCH 1/9] Rework doc step 1 Signed-off-by: Thomas Poignant --- .../index.md | 4 +- .../{data_collection => }/_category_.json | 2 +- docs/docs/configure_flag/_category_.json | 10 + docs/docs/{ => configure_flag}/flag_format.md | 18 +- .../configure_flag/rollout/_category_.json | 5 + .../{ => configure_flag}/rollout/canary.md | 0 .../rollout/experimentation.md | 2 +- .../{ => configure_flag}/rollout/index.md | 0 .../rollout/progressive.md | 0 .../{ => configure_flag}/rollout/scheduled.md | 0 .../target_user.md} | 9 +- docs/docs/faq.md | 4 +- docs/docs/flag_file/_category_.json | 5 - docs/docs/getting_started/_category_.json | 12 + .../using-go-module.md} | 4 +- .../docs/getting_started/using-openfeature.md | 9 + docs/docs/go_module/_category_.json | 10 + docs/docs/{ => go_module}/configuration.md | 27 +- .../go_module/data_collection/_category_.json | 5 + .../{ => go_module}/data_collection/custom.md | 4 + .../{ => go_module}/data_collection/file.md | 4 + .../data_collection/google_cloud_storage.md | 4 + .../{ => go_module}/data_collection/index.md | 6 +- .../{ => go_module}/data_collection/log.md | 4 + .../{ => go_module}/data_collection/s3.md | 4 + .../data_collection/webhook.md | 4 + docs/docs/go_module/notifier/_category_.json | 5 + docs/docs/{ => go_module}/notifier/custom.md | 4 + docs/docs/{ => go_module}/notifier/index.md | 4 + docs/docs/{ => go_module}/notifier/slack.md | 4 + docs/docs/{ => go_module}/notifier/webhook.md | 4 + docs/docs/index.md | 51 +- docs/docs/migrate_v0_v1.md | 17 + docs/docs/notifier/_category_.json | 5 - docs/docs/openfeature_sdk/_category_.json | 10 + docs/docs/openfeature_sdk/concepts.md | 33 + .../openfeature_sdk/configure_relay_proxy.md | 177 +++ .../getting_started_relay_proxy.md | 56 + .../openfeature_sdk/install_relay_proxy.md | 84 + docs/docs/openfeature_sdk/openfeature_java.md | 6 + .../openfeature_sdk/openfeature_javascript.md | 6 + .../openfeature_sdk/openfeature_typescript.md | 6 + .../openfeature_sdk/relay_proxy_endpoints.md | 187 +++ docs/docs/rollout/_category_.json | 5 - docs/docs/store_file/_category_.json | 5 + docs/docs/{flag_file => store_file}/custom.md | 4 + docs/docs/{flag_file => store_file}/file.md | 4 + docs/docs/{flag_file => store_file}/github.md | 4 + .../google_cloud_storage.md | 4 + docs/docs/{flag_file => store_file}/http.md | 4 + docs/docs/{flag_file => store_file}/index.md | 6 +- .../kubernetes_configmaps.md | 4 + docs/docs/{flag_file => store_file}/s3.md | 4 + docs/package-lock.json | 1365 ++++++----------- docs/package.json | 6 +- docs/static/docs/openfeature/concepts.jpg | Bin 0 -> 89608 bytes 56 files changed, 1227 insertions(+), 1003 deletions(-) rename docs/docs/{data_collection => }/_category_.json (72%) create mode 100644 docs/docs/configure_flag/_category_.json rename docs/docs/{ => configure_flag}/flag_format.md (92%) create mode 100644 docs/docs/configure_flag/rollout/_category_.json rename docs/docs/{ => configure_flag}/rollout/canary.md (100%) rename docs/docs/{ => configure_flag}/rollout/experimentation.md (98%) rename docs/docs/{ => configure_flag}/rollout/index.md (100%) rename docs/docs/{ => configure_flag}/rollout/progressive.md (100%) rename docs/docs/{ => configure_flag}/rollout/scheduled.md (100%) rename docs/docs/{users.md => configure_flag/target_user.md} (96%) delete mode 100644 docs/docs/flag_file/_category_.json create mode 100644 docs/docs/getting_started/_category_.json rename docs/docs/{quickstart.md => getting_started/using-go-module.md} (91%) create mode 100644 docs/docs/getting_started/using-openfeature.md create mode 100644 docs/docs/go_module/_category_.json rename docs/docs/{ => go_module}/configuration.md (88%) create mode 100644 docs/docs/go_module/data_collection/_category_.json rename docs/docs/{ => go_module}/data_collection/custom.md (96%) rename docs/docs/{ => go_module}/data_collection/file.md (98%) rename docs/docs/{ => go_module}/data_collection/google_cloud_storage.md (99%) rename docs/docs/{ => go_module}/data_collection/index.md (98%) rename docs/docs/{ => go_module}/data_collection/log.md (98%) rename docs/docs/{ => go_module}/data_collection/s3.md (99%) rename docs/docs/{ => go_module}/data_collection/webhook.md (98%) create mode 100644 docs/docs/go_module/notifier/_category_.json rename docs/docs/{ => go_module}/notifier/custom.md (95%) rename docs/docs/{ => go_module}/notifier/index.md (94%) rename docs/docs/{ => go_module}/notifier/slack.md (97%) rename docs/docs/{ => go_module}/notifier/webhook.md (99%) create mode 100644 docs/docs/migrate_v0_v1.md delete mode 100644 docs/docs/notifier/_category_.json create mode 100644 docs/docs/openfeature_sdk/_category_.json create mode 100644 docs/docs/openfeature_sdk/concepts.md create mode 100644 docs/docs/openfeature_sdk/configure_relay_proxy.md create mode 100644 docs/docs/openfeature_sdk/getting_started_relay_proxy.md create mode 100644 docs/docs/openfeature_sdk/install_relay_proxy.md create mode 100644 docs/docs/openfeature_sdk/openfeature_java.md create mode 100644 docs/docs/openfeature_sdk/openfeature_javascript.md create mode 100644 docs/docs/openfeature_sdk/openfeature_typescript.md create mode 100644 docs/docs/openfeature_sdk/relay_proxy_endpoints.md delete mode 100644 docs/docs/rollout/_category_.json create mode 100644 docs/docs/store_file/_category_.json rename docs/docs/{flag_file => store_file}/custom.md (96%) rename docs/docs/{flag_file => store_file}/file.md (96%) rename docs/docs/{flag_file => store_file}/github.md (98%) rename docs/docs/{flag_file => store_file}/google_cloud_storage.md (98%) rename docs/docs/{flag_file => store_file}/http.md (98%) rename docs/docs/{flag_file => store_file}/index.md (91%) rename docs/docs/{flag_file => store_file}/kubernetes_configmaps.md (97%) rename docs/docs/{flag_file => store_file}/s3.md (97%) create mode 100644 docs/static/docs/openfeature/concepts.jpg diff --git a/docs/blog/2022-07-01-feature-flags-devobs-podcast/index.md b/docs/blog/2022-07-01-feature-flags-devobs-podcast/index.md index 17e78fc579d..4167075f68c 100644 --- a/docs/blog/2022-07-01-feature-flags-devobs-podcast/index.md +++ b/docs/blog/2022-07-01-feature-flags-devobs-podcast/index.md @@ -5,8 +5,8 @@ tags: [GO Feature Flag, Openfeature, Podcast] --- ![](./devobs.jpg) -Earlier this week the super cool podcast Dev’Obs asked me to talk about feature flags, go-feature-flag and also the super nice initiative Open-feature. +Earlier this week the super cool podcast Dev’Obs asked me to talk about feature flags, go-feature-flag and also the super nice initiative OpenFeature. -The podcast is in French 🇫🇷 (yes I know sorry…) but you can learn a lot of cool things, when to use them, what is Open-feature about and all the cool things that the feature flags can bring to your developer experience. +The podcast is in French 🇫🇷 (yes I know sorry…) but you can learn a lot of cool things, when to use them, what is OpenFeature about and all the cool things that the feature flags can bring to your developer experience. Listen it here: https://www.listennotes.com/podcasts/devobs/devobs-24-feature-flags-rknRoFGL7N5/ diff --git a/docs/docs/data_collection/_category_.json b/docs/docs/_category_.json similarity index 72% rename from docs/docs/data_collection/_category_.json rename to docs/docs/_category_.json index 0811c99d70a..c640b3bdf2d 100644 --- a/docs/docs/data_collection/_category_.json +++ b/docs/docs/_category_.json @@ -1,5 +1,5 @@ { - "position": 70, + "position": 0, "collapsible": true, "collapsed": false } diff --git a/docs/docs/configure_flag/_category_.json b/docs/docs/configure_flag/_category_.json new file mode 100644 index 00000000000..93abd20b37d --- /dev/null +++ b/docs/docs/configure_flag/_category_.json @@ -0,0 +1,10 @@ +{ + "position": 20, + "collapsible": true, + "collapsed": true, + "label": "Configure your feature flags", + "link": { + "type": "generated-index", + "title": "Configure your feature flags" + } +} diff --git a/docs/docs/flag_format.md b/docs/docs/configure_flag/flag_format.md similarity index 92% rename from docs/docs/flag_format.md rename to docs/docs/configure_flag/flag_format.md index af745ab2757..90b7b093762 100644 --- a/docs/docs/flag_format.md +++ b/docs/docs/configure_flag/flag_format.md @@ -1,19 +1,23 @@ --- -sidebar_position: 30 +sidebar_position: 10 +description: What is a flag and how you can create them. --- -# Configure a flag +# How to configure a flag -`go-feature-flag` core feature is to centralize all your feature flags in a source file, and to avoid hosting and maintaining a backend server to manage them. +**GO Feature Flag** core feature is to centralize all your feature flags in a source file, and to avoid hosting and maintaining a backend server to manage them. Your file must be a valid `YAML`, `JSON` or `TOML` file with a list of flags *(examples: [`YAML`](https://github.com/thomaspoignant/go-feature-flag/tree/main/testdata/flag-config.yaml), [`JSON`](https://github.com/thomaspoignant/go-feature-flag/tree/main/testdata/flag-config.json), [`TOML`](https://github.com/thomaspoignant/go-feature-flag/tree/main/testdata/flag-config.toml))*. +:::tip The easiest way to create your configuration file is to used -[**GO Feature Flag Editor** available at https://thomaspoignant.github.io/go-feature-flag-editor/](https://thomaspoignant.github.io/go-feature-flag-editor/). +[**GO Feature Flag Editor** available at https://editor.gofeatureflag.org](https://editor.gofeatureflag.org/). + If you prefer to do it manually please follow instruction bellow. +::: ## Editor @@ -122,7 +126,7 @@ version = 12.0 | `disable` | *(optional)*
True if the flag is disabled.
**Default: `false`** | | `trackEvents` | *(optional)*
False if you don't want to export the data in your data exporter.
**Default: `true`** | | `version` | *(optional)*
The version is the version of your flag.
This number is used to display the information in the notifiers and data collection, you have to update it your self.
**Default: 0** | -| `rollout` | *(optional)*
rollout contains a specific rollout strategy you want to use.
**See [rollout section](rollout/index.md) for more details.** | +| `rollout` | *(optional)*
rollout contains a specific rollout strategy you want to use.
**See [rollout section](./rollout/index.md) for more details.** | ## Rule format @@ -197,5 +201,5 @@ rule: (env != "prod") or (user_id == 1234) You can have advanced configurations for your flag to have specific behavior for them, such as: -- [Specific rollout strategies](rollout/index.md) -- [Don't track a flag](data_collection/index.md#dont-track-a-flag) +- [Specific rollout strategies](./rollout/index.md) +- [Don't track a flag](../go_module/data_collection/index.md#dont-track-a-flag) diff --git a/docs/docs/configure_flag/rollout/_category_.json b/docs/docs/configure_flag/rollout/_category_.json new file mode 100644 index 00000000000..5a330427a1b --- /dev/null +++ b/docs/docs/configure_flag/rollout/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 30, + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/rollout/canary.md b/docs/docs/configure_flag/rollout/canary.md similarity index 100% rename from docs/docs/rollout/canary.md rename to docs/docs/configure_flag/rollout/canary.md diff --git a/docs/docs/rollout/experimentation.md b/docs/docs/configure_flag/rollout/experimentation.md similarity index 98% rename from docs/docs/rollout/experimentation.md rename to docs/docs/configure_flag/rollout/experimentation.md index 19f6461bf33..596d18f650e 100644 --- a/docs/docs/rollout/experimentation.md +++ b/docs/docs/configure_flag/rollout/experimentation.md @@ -83,7 +83,7 @@ A/B tests are widely considered the simplest form of controlled experiment. _**(source wikipedia)**_ ::: -To have a proper A/B testing solution with the module you should use the experimentation rollout combined with the [export of your data](../data_collection/). +To have a proper A/B testing solution with the module you should use the experimentation rollout combined with the [export of your data](../../go_module/data_collection/). This combination will allow to have your experimentation running for a dedicated time, and you will have the data to knows exactly which user was on which version of the flag. diff --git a/docs/docs/rollout/index.md b/docs/docs/configure_flag/rollout/index.md similarity index 100% rename from docs/docs/rollout/index.md rename to docs/docs/configure_flag/rollout/index.md diff --git a/docs/docs/rollout/progressive.md b/docs/docs/configure_flag/rollout/progressive.md similarity index 100% rename from docs/docs/rollout/progressive.md rename to docs/docs/configure_flag/rollout/progressive.md diff --git a/docs/docs/rollout/scheduled.md b/docs/docs/configure_flag/rollout/scheduled.md similarity index 100% rename from docs/docs/rollout/scheduled.md rename to docs/docs/configure_flag/rollout/scheduled.md diff --git a/docs/docs/users.md b/docs/docs/configure_flag/target_user.md similarity index 96% rename from docs/docs/users.md rename to docs/docs/configure_flag/target_user.md index fec9ff729cf..237a93a6b3a 100644 --- a/docs/docs/users.md +++ b/docs/docs/configure_flag/target_user.md @@ -1,7 +1,8 @@ --- -sidebar_position: 50 +sidebar_position: 20 +description: How to select who should have the flag activated. --- -# Targeting users with flags +# Target a user with a flag ## Users Feature flag targeting and rollouts are all determined by the user you pass to your Variation calls. @@ -21,9 +22,9 @@ user2 = ffuser.NewUserBuilder("user2-key"). Build() ``` -The most common attribute is the user's key and **this is the only mandatory user attribute.** +The most common attribute is the user's key and **this is the only mandatory user attribute.** The key should also uniquely identify each user. You can use a primary key, an e-mail address, or a hash, as long as the same user always has the same key. -**We recommend using a hash if possible.** +**We recommend using a hash if possible.** All the other attributes are optional. :::info diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 7206b383ab8..01c481258d7 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -1,5 +1,5 @@ --- -sidebar_position: 90 +sidebar_position: 100 --- # Frequently Asked Questions @@ -17,7 +17,7 @@ lifecycle of features. The lifecycle of your flags is key if you don't want to have un-used things everywhere in your code. 1. Start by creating the flag in your configuration file *(with 0% to avoid affecting your users)*. -2. Evaluate the flag in your code *(see [variation](users.md#variation))*. +2. Evaluate the flag in your code *(see [variation](./configure_flag/target_user.md#variation))*. 3. Deploy your application with the variation check. 4. Start rolling out your flag. 5. When 100% of your users have access to the new feature, remove the call to the variation from your code base. diff --git a/docs/docs/flag_file/_category_.json b/docs/docs/flag_file/_category_.json deleted file mode 100644 index 972b288ae45..00000000000 --- a/docs/docs/flag_file/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "position": 40, - "collapsible": true, - "collapsed": false -} diff --git a/docs/docs/getting_started/_category_.json b/docs/docs/getting_started/_category_.json new file mode 100644 index 00000000000..c813037664d --- /dev/null +++ b/docs/docs/getting_started/_category_.json @@ -0,0 +1,12 @@ +{ + "position": 10, + "label":"Getting Started", + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index", + "title": "Getting Started", + "description": "GO Feature Flag can be used in 2 different ways. You can use it as a GO module directly inside your code or by using an Open-feature SDK. To use the Open-feature SDKs you will need to deploy a relay proxy inside your architecture." + + } +} diff --git a/docs/docs/quickstart.md b/docs/docs/getting_started/using-go-module.md similarity index 91% rename from docs/docs/quickstart.md rename to docs/docs/getting_started/using-go-module.md index ce77daf6b47..941416b6d78 100644 --- a/docs/docs/quickstart.md +++ b/docs/docs/getting_started/using-go-module.md @@ -1,7 +1,8 @@ --- sidebar_position: 10 +description: Use the module in your GO application with nothing to install. --- -# Getting started +# Using the GO module ## Installation ```bash @@ -9,7 +10,6 @@ go get github.com/thomaspoignant/go-feature-flag ``` ## SDK Initialisation - First, you need to initialize the `ffclient` with the location of your backend file. ```go linenums="1" err := ffclient.Init(ffclient.Config{ diff --git a/docs/docs/getting_started/using-openfeature.md b/docs/docs/getting_started/using-openfeature.md new file mode 100644 index 00000000000..99c624d7fa0 --- /dev/null +++ b/docs/docs/getting_started/using-openfeature.md @@ -0,0 +1,9 @@ +--- +sidebar_position: 20 +description: Deploy the relay proxy and use the OpenFeature SDKs +--- +# Using OpenFeature SDKs + +## Install + +Start by installing diff --git a/docs/docs/go_module/_category_.json b/docs/docs/go_module/_category_.json new file mode 100644 index 00000000000..215e2ea86ba --- /dev/null +++ b/docs/docs/go_module/_category_.json @@ -0,0 +1,10 @@ +{ + "position": 40, + "label":"Use as a GO module", + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index", + "title": "Use as a GO module" + } +} diff --git a/docs/docs/configuration.md b/docs/docs/go_module/configuration.md similarity index 88% rename from docs/docs/configuration.md rename to docs/docs/go_module/configuration.md index a682977b8d1..fdabd258c50 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/go_module/configuration.md @@ -1,5 +1,6 @@ --- -sidebar_position: 20 +sidebar_position: 10 +description: How to configure the GO module to use it directly in your code. --- # Configuration @@ -10,18 +11,18 @@ During the initialization you must give a [`ffclient.Config{}`](https://pkg.go.d ## Configuration fields -| Field | Description | -|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `Retriever` | The configuration retriever you want to use to get your flag file
*See [Store your flag file](flag_file/index.md) for the configuration details*. | -| `Context` | *(optional)*
The context used by the retriever.
Default: `context.Background()` | -| `Environment` | *(optional)*
The environment the app is running under, can be checked in feature flag rules.
Default: `""`
*Check [**"environments"** section](./flag_format/#environments) to understand how to use this parameter.* | -| `DataExporter` | *(optional)*
DataExporter defines how to export data on how your flags are used.
*see [export data section](data_collection/index.md) for more details*. | -| `FileFormat` | *(optional)*
Format of your configuration file. Available formats are `yaml`, `toml` and `json`, if you omit the field it will try to unmarshal the file as a `yaml` file.
Default: `YAML` | -| `Logger` | *(optional)*
Logger used to log what `go-feature-flag` is doing.
If no logger is provided the module will not log anything.
Default: No log | -| `Notifiers` | *(optional)*
List of notifiers to call when your flag file has been changed.
*See [notifiers section](./notifier/index.md) for more details*. | -| `PollingInterval` | (optional) Duration to wait before refreshing the flags.
The minimum polling interval is 1 second.
Default: 60 * time.Second | -| `StartWithRetrieverError` | *(optional)* If **true**, the SDK will start even if we did not get any flags from the retriever. It will serve only default values until the retriever returns the flags.
The init method will not return any error if the flag file is unreachable.
Default: **false** | -| `Offline` | *(optional)* If **true**, the SDK will not try to retrieve the flag file and will not export any data. No notification will be send neither.
Default: false | +| Field | Description | +|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Retriever` | The configuration retriever you want to use to get your flag file
*See [Store your flag file](../store_file/index.md) for the configuration details*. | +| `Context` | *(optional)*
The context used by the retriever.
Default: `context.Background()` | +| `Environment` | *(optional)*
The environment the app is running under, can be checked in feature flag rules.
Default: `""`
*Check [**"environments"** section](../configure_flag/flag_format/#environments) to understand how to use this parameter.* | +| `DataExporter` | *(optional)*
DataExporter defines how to export data on how your flags are used.
*see [export data section](data_collection/index.md) for more details*. | +| `FileFormat` | *(optional)*
Format of your configuration file. Available formats are `yaml`, `toml` and `json`, if you omit the field it will try to unmarshal the file as a `yaml` file.
Default: `YAML` | +| `Logger` | *(optional)*
Logger used to log what `go-feature-flag` is doing.
If no logger is provided the module will not log anything.
Default: No log | +| `Notifiers` | *(optional)*
List of notifiers to call when your flag file has been changed.
*See [notifiers section](./notifier/index.md) for more details*. | +| `PollingInterval` | (optional) Duration to wait before refreshing the flags.
The minimum polling interval is 1 second.
Default: 60 * time.Second | +| `StartWithRetrieverError` | *(optional)* If **true**, the SDK will start even if we did not get any flags from the retriever. It will serve only default values until the retriever returns the flags.
The init method will not return any error if the flag file is unreachable.
Default: **false** | +| `Offline` | *(optional)* If **true**, the SDK will not try to retrieve the flag file and will not export any data. No notification will be send neither.
Default: false | ## Example ```go diff --git a/docs/docs/go_module/data_collection/_category_.json b/docs/docs/go_module/data_collection/_category_.json new file mode 100644 index 00000000000..5a330427a1b --- /dev/null +++ b/docs/docs/go_module/data_collection/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 30, + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/data_collection/custom.md b/docs/docs/go_module/data_collection/custom.md similarity index 96% rename from docs/docs/data_collection/custom.md rename to docs/docs/go_module/data_collection/custom.md index 8f03b775cac..13922eb8232 100644 --- a/docs/docs/data_collection/custom.md +++ b/docs/docs/go_module/data_collection/custom.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 30 +--- + # Custom exporter To create a custom exporter you must have a `struct` that implements the [`exporter.Exporter`](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/internal/exporter#Exporter) interface. diff --git a/docs/docs/data_collection/file.md b/docs/docs/go_module/data_collection/file.md similarity index 98% rename from docs/docs/data_collection/file.md rename to docs/docs/go_module/data_collection/file.md index 86625081fc0..a606fb5a8ed 100644 --- a/docs/docs/data_collection/file.md +++ b/docs/docs/go_module/data_collection/file.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 3 +--- + # File Exporter The file exporter will collect the data and create a new file in a specific folder everytime we send the data. This file should be in the local instance. diff --git a/docs/docs/data_collection/google_cloud_storage.md b/docs/docs/go_module/data_collection/google_cloud_storage.md similarity index 99% rename from docs/docs/data_collection/google_cloud_storage.md rename to docs/docs/go_module/data_collection/google_cloud_storage.md index 5f51f02cc99..fa394e3f1df 100644 --- a/docs/docs/data_collection/google_cloud_storage.md +++ b/docs/docs/go_module/data_collection/google_cloud_storage.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 2 +--- + # Google Cloud Storage Exporter The **Google Cloud Storage exporter** will collect the data and create a new file in a specific folder everytime we send the data. diff --git a/docs/docs/data_collection/index.md b/docs/docs/go_module/data_collection/index.md similarity index 98% rename from docs/docs/data_collection/index.md rename to docs/docs/go_module/data_collection/index.md index 9823bded0e1..d53d85fac86 100644 --- a/docs/docs/data_collection/index.md +++ b/docs/docs/go_module/data_collection/index.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 0 +--- + # Export data If you want to export data about how your flag are used, you can use the **`DataExporter`**. @@ -53,7 +57,7 @@ In your `ffclient.Config` add the `DataExporter` field and configure your export To avoid spamming your location everytime you have a variation called, `go-feature-flag` is storing in memory all the events and send them in bulk to the exporter. You can decide the threshold on when to send the data with the properties `FlushInterval` and `MaxEventInMemory`. The first threshold hit will export the data. -If there are some flags you don't want to export, you can use `trackEvents` fields on these specific flags to disable the data export *(see [flag file format](../flag_format.md))*. +If there are some flags you don't want to export, you can use `trackEvents` fields on these specific flags to disable the data export *(see [flag file format](../../configure_flag/flag_format.md))*. ### Example diff --git a/docs/docs/data_collection/log.md b/docs/docs/go_module/data_collection/log.md similarity index 98% rename from docs/docs/data_collection/log.md rename to docs/docs/go_module/data_collection/log.md index 30c0205c9b4..f4eec1f0ff1 100644 --- a/docs/docs/data_collection/log.md +++ b/docs/docs/go_module/data_collection/log.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 5 +--- + # Log Exporter The log exporter is here mostly for backward compatibility *(originally every variation were logged, but it can be a lot of data for a default configuration)*. It will use your logger `ffclient.Config.Logger` to log every variation changes. diff --git a/docs/docs/data_collection/s3.md b/docs/docs/go_module/data_collection/s3.md similarity index 99% rename from docs/docs/data_collection/s3.md rename to docs/docs/go_module/data_collection/s3.md index eaac21a8c99..16208070dd3 100644 --- a/docs/docs/data_collection/s3.md +++ b/docs/docs/go_module/data_collection/s3.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 1 +--- + # S3 Exporter The **S3 exporter** will collect the data and create a new file in a specific folder everytime we send the data. diff --git a/docs/docs/data_collection/webhook.md b/docs/docs/go_module/data_collection/webhook.md similarity index 98% rename from docs/docs/data_collection/webhook.md rename to docs/docs/go_module/data_collection/webhook.md index dd15819e81e..0324efd6c27 100644 --- a/docs/docs/data_collection/webhook.md +++ b/docs/docs/go_module/data_collection/webhook.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 4 +--- + # Webhook Exporter The **Webhook exporter** will collect the data and send them via an HTTP POST request to the specified endpoint. diff --git a/docs/docs/go_module/notifier/_category_.json b/docs/docs/go_module/notifier/_category_.json new file mode 100644 index 00000000000..5a330427a1b --- /dev/null +++ b/docs/docs/go_module/notifier/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 30, + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/notifier/custom.md b/docs/docs/go_module/notifier/custom.md similarity index 95% rename from docs/docs/notifier/custom.md rename to docs/docs/go_module/notifier/custom.md index f6ae9f51bcc..ff7a3a091bc 100644 --- a/docs/docs/notifier/custom.md +++ b/docs/docs/go_module/notifier/custom.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 30 +--- + # Custom Notifier To create a custom notifier you must have a `struct` that implements the diff --git a/docs/docs/notifier/index.md b/docs/docs/go_module/notifier/index.md similarity index 94% rename from docs/docs/notifier/index.md rename to docs/docs/go_module/notifier/index.md index af4d9568b9f..3cad29c1332 100644 --- a/docs/docs/notifier/index.md +++ b/docs/docs/go_module/notifier/index.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 1 +--- + # Notify flag changes If you want to be informed when a flag has changed, you can configure a [**notifier**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag#NotifierConfig). diff --git a/docs/docs/notifier/slack.md b/docs/docs/go_module/notifier/slack.md similarity index 97% rename from docs/docs/notifier/slack.md rename to docs/docs/go_module/notifier/slack.md index e6e9564a0dc..8ff6b890ba8 100644 --- a/docs/docs/notifier/slack.md +++ b/docs/docs/go_module/notifier/slack.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 1 +--- + # Slack Notifier The **Slack** notifier allows you to get notification on your favorite slack channel when an instance of `go-feature-flag` is detecting changes in the configuration file. diff --git a/docs/docs/notifier/webhook.md b/docs/docs/go_module/notifier/webhook.md similarity index 99% rename from docs/docs/notifier/webhook.md rename to docs/docs/go_module/notifier/webhook.md index 71509a6982e..22a3962de28 100644 --- a/docs/docs/notifier/webhook.md +++ b/docs/docs/go_module/notifier/webhook.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 2 +--- + # Webhook Notifier The **Webhook notifier** will perform an HTTP POST request to the specified endpoint everytime that a change in the flags is detected. diff --git a/docs/docs/index.md b/docs/docs/index.md index d8a7017082d..9931d77b949 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -8,43 +8,30 @@ sidebar_position: 1 go-feature-flag logo

-

- Build Status - Coverage Status - Sonarcloud Status - Build Status -
- Release version - GoDoc - Go version - License - Mentioned in Awesome Go - Join us on slack -

- -# go-feature-flag - -**Feature flags with no complex system to maintain!** - -## What is go-feature-flag? +## What is GO Feature Flag? +GO Feature Flag is a simple, complete and lightweight feature flag solution 100% opensource. -A simple and complete feature flag solution, without any complex backend system to install, all you need is a file as your backend. +The solution has been built to start experiencing the usage of feature flags in your code without having to contract with any vendor. -No server is needed, just add a file to your central system and all your services will react to the changes in this file. +**GO Feature Flag** has started to be a solution only for the GO language, but with the new standardisation of feature flags by [Openfeature](https://openfeature.dev/) project, +now the solution is available for multiple languages _(`JAVA`, `typescript`, `javascript`, ...)_ with a simple server to host. -If you are not familiar with feature flags, also called feature Toggles, you can read this [article from Martin Fowler](https://www.martinfowler.com/articles/feature-toggles.html) +:::info +If you are not familiar with feature flags, also called feature toggles, you can read this [article from Martin Fowler](https://www.martinfowler.com/articles/feature-toggles.html) where he explains why this is a great pattern. -I've also written an [article](https://medium.com/better-programming/feature-flags-and-how-to-iterate-quickly-7e3371b9986) that explains why feature flags can fasten your iteration cycle. +I've also written an [article](https://medium.com/better-programming/feature-flags-and-how-to-iterate-quickly-7e3371b9986) which explains why feature flags can fasten your iteration cycle. +::: -**go-feature-flags supports:** +## What can I do with GO Feature Flag? -- Storing your configuration flags file on various locations ([`HTTP`](./flag_file/http.md), [`S3`](./flag_file/s3.md), [`GitHub`](./flag_file/github.md), [`file`](./flag_file/file.md)). -- Configuring your flags in various [format](flag_format.md) (`JSON`, `TOML` and `YAML`). -- Adding complex [rules](flag_format.md#rule-format) to target your users. +- Storing your configuration flags file on various locations ([`HTTP`](./store_file/http.md), [`S3`](./store_file/s3.md), [`GitHub`](./store_file/github.md), [`file`](./store_file/file.md)). +- Configuring your flags in various [format](configure_flag/flag_format.md) (`JSON`, `TOML` and `YAML`). +- Adding complex [rules](configure_flag/flag_format.md#rule-format) to target your users. - Use complex rollout strategy for your flags : - - [Run A/B testing experimentation](rollout/experimentation.md). - - [Progressively rollout a feature](rollout/progressive.md). - - [Schedule your flag updates](rollout/scheduled.md). -- Exporting your flags usage data ([`s3`](data_collection/s3.md), [`log`](data_collection/log.md) and [`file`](data_collection/file.md)). -- Getting notified when a flag has been changed ([`webhook`](notifier/webhook.md) and [`slack`](notifier/slack.md)). + - [Run A/B testing experimentation](configure_flag/rollout/experimentation.md). + - [Progressively rollout a feature](configure_flag/rollout/progressive.md). + - [Schedule your flag updates](configure_flag/rollout/scheduled.md). +- Exporting your flags usage data ([`s3`](go_module/data_collection/s3.md), [`log`](go_module/data_collection/log.md) and [`file`](go_module/data_collection/file.md)). +- Getting notified when a flag has been changed ([`webhook`](go_module/notifier/webhook.md) and [`slack`](go_module/notifier/slack.md)). +- Use GO Feature Flag in several languages. diff --git a/docs/docs/migrate_v0_v1.md b/docs/docs/migrate_v0_v1.md new file mode 100644 index 00000000000..a03125ee59d --- /dev/null +++ b/docs/docs/migrate_v0_v1.md @@ -0,0 +1,17 @@ +--- +sidebar_position: 90 +description: How to migrate from v0.x.x to v1.x.x +--- + +# Migrate from v0.x.x to v1.x.x + +:::info +Version `v1.0.0` has introduced a new flag format that push the limits of **GO Feature Flag** even further. +**BUT** the flag format from all the versions `v0.x.x` are still compatible and supported by the `v1.0.0`. +::: + +## Install the migration command line + +## Use the migration command line + +## Update your flag file diff --git a/docs/docs/notifier/_category_.json b/docs/docs/notifier/_category_.json deleted file mode 100644 index c856d354c3a..00000000000 --- a/docs/docs/notifier/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "position": 80, - "collapsible": true, - "collapsed": false -} diff --git a/docs/docs/openfeature_sdk/_category_.json b/docs/docs/openfeature_sdk/_category_.json new file mode 100644 index 00000000000..0140afc37a8 --- /dev/null +++ b/docs/docs/openfeature_sdk/_category_.json @@ -0,0 +1,10 @@ +{ + "position": 50, + "label":"Use with OpenFeature", + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index", + "title": "Use with OpenFeature" + } +} diff --git a/docs/docs/openfeature_sdk/concepts.md b/docs/docs/openfeature_sdk/concepts.md new file mode 100644 index 00000000000..0efb3fc7b26 --- /dev/null +++ b/docs/docs/openfeature_sdk/concepts.md @@ -0,0 +1,33 @@ +--- +sidebar_position: 10 +description: How GO Feature Flag is working with OpenFeature. +--- + +# Concepts + +## What is OpenFeature? +:::note +OpenFeature is an open standard for feature flag management, created to support a robust feature flag ecosystem using cloud native technologies. OpenFeature provides a unified API and SDK, and a developer-first, cloud-native implementation, with extensibility for open source and commercial offerings. + +https://docs.openfeature.dev/docs/category/concepts +::: + +OpenFeature offer a framework-agnostic way of using feature flags, it means that if you use OpenFeature SDKs you will have minimum changes to do if you want to choose another provider. + +To support this initiative, **GO Feature Flag** does not have any SDKs and rely 100% on OpenFeature SDKs. +To be compatible with our solution, we offer [`providers`](https://docs.openfeature.dev/docs/reference/concepts/provider) for GO Feature Flag in several languages. + + +## How OpenFeature and GO Feature Flag are working together? + +To use the OpenFeature SDKs you need what we call a provider. +A **provider** is responsible for performing flag evaluations. It provides an abstraction between **GO Feature Flag** and the OpenFeature SDK. + +A provider need a backend service to perform the flag evaluation. This is why we have introduced the **relay proxy**. +This component retrieve your feature flag configuration file using the GO module and expose APIs to get your flags variations. + +![](/docs/openfeature/concepts.jpg) + +With this simple architecture you have a central component _(the relay proxy)_ that is in charge of the flag evaluation, while the SDKs and providers are responsible to communicate with the relay proxy. + +This supports different languages the same way and makes you able to use GO Feature Flag with all your favorite languages. diff --git a/docs/docs/openfeature_sdk/configure_relay_proxy.md b/docs/docs/openfeature_sdk/configure_relay_proxy.md new file mode 100644 index 00000000000..aa8ecc6b3bd --- /dev/null +++ b/docs/docs/openfeature_sdk/configure_relay_proxy.md @@ -0,0 +1,177 @@ +--- +sidebar_position: 30 +description: How to configure the relay proxy to serve your feature flags. +--- + +# Configure the relay proxy + +## Global configurationuse +The configuration of the **relay proxy** is based on a configuration file that you have to provide. + +| Field name | Type | Default | Description | +|---------------------------|-------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `retriever` | [retriever](#retriever) | **none** | **(mandatory)** This is the configuration on how to retrieve the configuration of the files | +| `listen` | int | `1031` | This is the port used by the relay proxy when starting the HTTP server. | +| `pollingInterval` | int | `60000` | This is the time interval **in millisecond** when the relay proxy is reloading the configuration file.
The minimum time accepted is 1000 millisecond. | +| `hideBanner` | boolean | `false` | Should we display the beautiful **go-feature-flag** banner when starting the relay proxy | +| `enableSwagger` | boolean | `false` | Do you want to enable swagger to test the APIs directly. If you are enabling Swagger you will have to provide the `host` configuration and the Swagger UI will be available at `http://:/swagger/`. | +| `host` | string | `localhost` | This is the DNS you will use to access the relay proxy. This field is used by Swagger to query the API at the right place. | +| `restApiTimeout` | int | `5000` | Timeout in millisecond we are accepting to wait in our APIs. | +| `debug` | boolean | `false` | If `true` you will have more logs in the output that will help you to better understand what happen. If an error happen in the API the error will be also shown in the body. | +| `fileFormat` | string | `yaml` | This is the format of your `go-feature-flag` configuration file. Acceptable values are `yaml`, `json`, `toml`. | +| `startWithRetrieverError` | boolean | `false` | By default the **relay proxy** will crash if he is not able to retrieve the flags from the configuration.
If you don't want your relay proxy to crash, you can set `startWithRetrieverError` to true. Until the flag is retrievable the relay proxy will only answer with default values. | +| `exporter` | [exporter](#exporter) | **none** | Exporter is the configuration on how to export data. | +| `notifier` | [notifier](#notifier) | **none** | Notifiers is the configuration on where to notify a flag change. | + + + + +## type `retriver` + +`go-feature-flag` is supporting different kind of retriever such as S3, Google store, etc ... +In this section we will present all the available retriever configuration available. + +### S3 + +| Field name | Type | Default | Description | +|------------|--------|----------|--------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** This is the name of your S3 bucket _(ex: `my-featureflag-bucket`)_. | +| `item` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | + + +### GitHub + +| Field name | Type | Default | Description | +|------------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`github`**.
_This field is mandatory and describe which retriever you are using._ | +| `repositorySlug` | string | **none** | **(mandatory)** The repository slug of the GitHub repository where your file is located _(ex: `thomaspoignant/go-feature-flag`)_. | +| `path` | string | **none** | **(mandatory)** Path to the file inside the repository _(ex: `config/flag/my-flags.yaml`)_. | +| `branch` | string | `main` | The branch we should check in the repository. | +| `githubToken` | string | **none** | Github token is used to access a private repository, you need the repo permission ([how to create a GitHub token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)). | +| `timeout` | string | `10000` | Timeout in millisecond used when calling GitHub. | + +### File + +| Field name | Type | Default | Description | +|------------|--------|----------|----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | +| `path` | string | **none** | **(mandatory)** Path to the file in your local computer _(ex: `/goff/my-flags.yaml`)_. | + + +### HTTP + +| Field name | Type | Default | Description | +|------------|---------------------|----------|----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`http`**.
_This field is mandatory and describe which retriever you are using._ | +| `url` | string | **none** | **(mandatory)** Location where to retrieve the file. | +| `method` | string | `GET` | The HTTP Method you are using to call the HTTP endpoint. | +| `body` | string | **none** | The HTTP Body you are using to call the HTTP endpoint. | +| `headers` | map[string][]string | **none** | The HTTP headers used to call when calling the HTTP endpoint (useful for authorization). | +| `timeout` | string | `10000` | Timeout in millisecond used when calling the HTTP endpoint. | + + +### Google Storage + +| Field name | Type | Default | Description | +|------------|--------|----------|-------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`googleStorage`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** This is the name of your Google Storage bucket _(ex: `my-featureflag-bucket`)_. | +| `object` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | + + +### Kubernetes ConfigMap + +_Note that relay proxy is only supporting this while running inside the kubernetes cluster._ + +| Field name | Type | Default | Description | +|-------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`configmap`**.
_This field is mandatory and describe which retriever you are using._ | +| `namespace` | string | **none** | **(mandatory)** This is the name of the namespace where your **configmap** is located _(ex: `default`)_. | +| `configmap` | string | **none** | **(mandatory)** Name of the **configmap** we should read _(ex: `feature-flag`)_. | +| `key` | string | **none** | **(mandatory)** Name of the `key` in the **configmap** which contains the flag. | + + + +## type `exporter` + +### Webhook + +| Field name | Type | Default | Description | +|--------------------|-------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`webhook`**.
_This field is mandatory and describe which retriever you are using._ | +| `endpointUrl` | string | **none** | **(mandatory)** EndpointURL of your webhook. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | +| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | + + +### File + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | +| `outputDir` | string | **none** | **(mandatory)** OutputDir is the location of the directory where to store the exported files. It should finish with a `/`. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` + + +### Log + +| Field name | Type | Default | Description | +|--------------------|--------|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`log`**.
_This field is mandatory and describe which retriever you are using._ | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `logFormat` | string | `[{{ .FormattedDate}}] user="{{ .UserKey}}", flag="{{ .Key}}", value="{{ .Value}}"` | LogFormat is the [template](https://golang.org/pkg/text/template/) configuration of the output format of your log.
You can use all the key from the exporter.FeatureEvent + a key called FormattedDate that represent the date with the RFC 3339 Format. | + +### S3 + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** Name of your S3 Bucket. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` +| `path` | string | **bucket root level** | The location of the directory in S3. | + +### Google Storage + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** Name of your Google Cloud Storage Bucket. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` +| `path` | string | **bucket root level** | The location of the directory in S3. | + + + + +## type `notifier` + +### Slack + +| Field name | Type | Default | Description | +|-------------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | +| `slackWebhookUrl` | string | **none** | **(mandatory)** The complete URL of your incoming webhook configured in Slack. | + +### Webhook + +| Field name | Type | Default | Description | +|----------------|---------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | +| `endpointUrl` | string | **none** | **(mandatory)** The complete URL of your API (we will send a POST request to this URL, see [format](https://thomaspoignant.github.io/go-feature-flag/latest/notifier/webhook/#format) | +| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | +| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | diff --git a/docs/docs/openfeature_sdk/getting_started_relay_proxy.md b/docs/docs/openfeature_sdk/getting_started_relay_proxy.md new file mode 100644 index 00000000000..abf98a60d58 --- /dev/null +++ b/docs/docs/openfeature_sdk/getting_started_relay_proxy.md @@ -0,0 +1,56 @@ +--- +sidebar_position: 21 +description: Getting started with the relay proxy. +--- + +# Getting started with the relay proxy + +Before starting your **relay proxy** you will need to create a minimal configuration file. + +```yaml +# this is a minimal config containing only where your flag file is located +retriever: + kind: http + url: https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/examples/file/flags.yaml +``` + +After that you can launch the **relay proxy** by using this command: +```shell +go-feature-flag-relay-proxy --config=/path/to/your/configfile +``` + +The **relay proxy** will read the configuration file and retrieve all the flags. +After that you can use all the available endpoints _(see **Service endpoints** section)_ and get the variations for your users. + + +## Deployment options + +A common way to run **go-feature-flag relay proxy** is to use the Docker Container. +An image is available on docker Hub [`thomaspoignant/go-feature-flag-relay-proxy`](https://hub.docker.com/r/thomaspoignant/go-feature-flag-relay-proxy). + +You can also run it as a service in your application following the **Installation** section. + +## Specifying a configuration + +To configure the relay proxy you should provide a configuration file when launching the instance. + +The easiest way to provide the file is to use the option `--config=/path_to_your_file.yaml`. +But if you don't provide this option, the relay proxy will look in these folders if a file named `goff-proxy.yaml` is available. + +- **current folder** +- `/goff/` +- `/etc/opt/goff/` + +To learn how to configure the relay proxy, read [Configuration](./configure_relay_proxy). + +## Exporting metrics and traces + +To export the data you can use all the capabilities of `go-feature-flag` SDK. +To configure it please refer to the [type `exporter` section](./configure_relay_proxy#exporter) of the configuration. + +## Service endpoints +The Relay Proxy defines many HTTP/HTTPS endpoints. +Most of these are proxies for GO Feature Flag services, to be used by SDKs that connect to the Relay Proxy. +Others are specific to the Relay Proxy, such as for monitoring its status. + +Please refer to [endpoints documentation](./relay_proxy_endpoints) to get the full details of what exists in our REST API. diff --git a/docs/docs/openfeature_sdk/install_relay_proxy.md b/docs/docs/openfeature_sdk/install_relay_proxy.md new file mode 100644 index 00000000000..0888dbddb2d --- /dev/null +++ b/docs/docs/openfeature_sdk/install_relay_proxy.md @@ -0,0 +1,84 @@ +--- +sidebar_position: 20 +description: Relay proxy is the component that will challenge the flags, this page explain how to install it. +--- + +# Install the relay proxy + +## When should I use the GO Feature Flag Relay Proxy? +- If you want to access your feature flag using an API instead of the [`thomaspoignant/go-feature-flag`](https://github.com/thomaspoignant/go-feature-flag) SDK. +- If you are not using GOlang to build your application. +- If you want to reduce the number of accesses to your configuration flag by centralizing them. + + +## Install using Homebrew (mac and linux) +```shell +brew tap thomaspoignant/homebrew-tap +brew install go-feature-flag-relay-proxy +``` + +## Install using Scoop (windows) +```shell +scoop bucket add org https://github.com/thomaspoignant/scoop.git +scoop install go-feature-flag-relay-proxy +``` + +## Install using docker +```shell +docker pull thomaspoignant/go-feature-flag-relay-proxy:latest +``` +:::info +More info in the [dockerhub page](https://hub.docker.com/r/thomaspoignant/go-feature-flag-relay-proxy). +::: + +## Getting started + +Before starting your **relay proxy** you will need to create a minimal configuration file. + +```yaml +# this is a minimal config containing only where your flag file is located +retriever: + kind: http + url: https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/examples/file/flags.yaml +``` + +After that you can launch the **relay proxy** by using this command: +```shell +go-feature-flag-relay-proxy --config=/path/to/your/configfile +``` + +The **relay proxy** will read the configuration file and retrieve all the flags. +After that you can use all the available endpoints _(see **Service endpoints** section)_ and get the variations for your users. + + +## Deployment options + +A common way to run **go-feature-flag relay proxy** is to use the Docker Container. +An image is available on docker Hub [`thomaspoignant/go-feature-flag-relay-proxy`](https://hub.docker.com/r/thomaspoignant/go-feature-flag-relay-proxy). + +You can also run it as a service in your application following the **Installation** section. + +## Specifying a configuration + +To configure the relay proxy you should provide a configuration file when launching the instance. + +The easiest way to provide the file is to use the option `--config=/path_to_your_file.yaml`. +But if you don't provide this option, the relay proxy will look in these folders if a file named `goff-proxy.yaml` is available. + +- **current folder** +- `/goff/` +- `/etc/opt/goff/` + +To learn how to configure the relay proxy, read [Configuration](../go_module/configuration). + +## Exporting metrics and traces + +To export the data you can use all the capabilities of `go-feature-flag` SDK. +To configure it please refer to the [type `exporter` section](../go_module/configuration#exporter) of the configuration. + +## Service endpoints +The Relay Proxy defines many HTTP/HTTPS endpoints. +Most of these are proxies for GO Feature Flag services, to be used by SDKs that connect to the Relay Proxy. +Others are specific to the Relay Proxy, such as for monitoring its status. + +Please refer to [endpoints documentation](./relay_proxy_endpoints) to get the full details of what exists in our REST API. diff --git a/docs/docs/openfeature_sdk/openfeature_java.md b/docs/docs/openfeature_sdk/openfeature_java.md new file mode 100644 index 00000000000..e9ed90d8ac2 --- /dev/null +++ b/docs/docs/openfeature_sdk/openfeature_java.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 40 +description: How to use the OpenFeature JAVA SDK +--- + +# JAVA SDK usage diff --git a/docs/docs/openfeature_sdk/openfeature_javascript.md b/docs/docs/openfeature_sdk/openfeature_javascript.md new file mode 100644 index 00000000000..4177feb4e61 --- /dev/null +++ b/docs/docs/openfeature_sdk/openfeature_javascript.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 51 +description: How to use the OpenFeature Javascript SDK +--- + +# Javascript SDK usage diff --git a/docs/docs/openfeature_sdk/openfeature_typescript.md b/docs/docs/openfeature_sdk/openfeature_typescript.md new file mode 100644 index 00000000000..d8f9fd7edf4 --- /dev/null +++ b/docs/docs/openfeature_sdk/openfeature_typescript.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 50 +description: How to use the OpenFeature Typescript SDK +--- + +# Typescript SDK usage diff --git a/docs/docs/openfeature_sdk/relay_proxy_endpoints.md b/docs/docs/openfeature_sdk/relay_proxy_endpoints.md new file mode 100644 index 00000000000..2f722758936 --- /dev/null +++ b/docs/docs/openfeature_sdk/relay_proxy_endpoints.md @@ -0,0 +1,187 @@ +--- +sidebar_position: 60 +description: Description of the available endpoints in the relay proxy. +--- + +# Relay proxy endpoints + +The most updated documentation about the relay proxy endpoints is the Swagger docs _(see [Swagger section](#swagger) to see how to access to the documentation)_. + +## Specific to Relay Proxy + +### Health (health check) +Making a `GET` request to the URL path `/health` will tell you if the **relay proxy** is ready to serve traffic. +This is useful especially for loadbalancer to know that they can send traffic to the service. + +```json +{ "initialized": true } +``` + +### Info +Making a `GET` request to the URL path `/info` will tell give you information about the actual state of the **relay proxy**. +As of Today the level of information is small be we can improve this endpoint to returns more information. + +```json +{ + "cacheRefresh": "2022-06-13T11:22:55.941628+02:00" +} +``` + +| Field name | Type | Description | +|--------------------|------|-------------------------------------------------------------------------------------| +| `cacheRefresh` | date | This is the last time when your flag file was read and store in the internal cache. | + + +### Swagger +Swagger endpoint is serving a [swagger UI](https://swagger.io/tools/swagger-ui/) to test your REST endpoints. +By default, this endpoint is not exposed, you need to have this configuration in your **relay proxy** configuration file: + +```yaml +# ... +enableSwagger: true +host: my-proxy-domain.com # the DNS to access the proxy +``` + +When enabled, you can go to the `/swagger/` endpoint with your browser, and you will have access to the Swagger UI for the relay proxy. + +## Proxies for GO Feature Flag services + +### Endpoint to get variation for a flag + +Making a `POST` request to the URL `/v1/feature//eval` will give you the value of the flag for this user. +To get a variation you should provide information about the user. +For that you should provide some user information in `JSON` in the request body. + +#### Request +**Example:** +```json +{ + "user": { + "key": "123e4567-e89b-12d3-a456-426614174000", + "anonymous": false, + "custom": { + "firstname": "John", + "lastname": "Doe", + "email": "john.doe@random.io" + } + }, + "defaultValue": "default_value_provided_by_SDK" +} +``` + +| Field name | Type | Description | +|--------------------|--------------------|--------------------------------------------------------------------------------------------| +| `user` | [user](#user_type) | **(mandatory)** The representation of a user for your feature flag system. | +| `defaultValue` | any | **(mandatory)** The value will we use if we are not able to get the variation of the flag. | + + +#### Response + +Based on the name of the flag and the user you will always have a response for the variation. +The API will respond with a `200` even if the flag is not available, the goal is for your application to not crash even if +the flag does not exist anymore. + +**Example:** +```json +{ + "value": "welcome_new_feature", + "variationType": "true", + "version": "0", + "trackEvents": true, + "failed": false +} +``` + + + +| Field name | Type | Description | +|-----------------|---------|--------------------------------------------------------------------------------------------------------------------| +| `value` | any | The flag value for this user. | +| `variationType` | string | The variation used to give you this value. | +| `version` | string | The version of the flag used. | +| `trackEvents` | boolean | `true` if the event was tracked by the relay proxy. | +| `failed` | boolean | `true` if something went wrong in the relay proxy _(flag does not exists, ...)_ and we serve the **defaultValue**. | + + + +### Endpoint to get all flags variations for a user + +Making a `POST` request to the URL `/v1/allflags` will give you the values of all the flags for this user. +To get a variation you should provide information about the user. +For that you should provide some user information in `JSON` in the request body. + +#### Request +**Example:** +```json +{ + "user": { + "key": "123e4567-e89b-12d3-a456-426614174000", + "anonymous": false, + "custom": { + "firstname": "John", + "lastname": "Doe", + "email": "john.doe@random.io" + } + } +} +``` + +| Field name | Type | Description | +|--------------------|--------------------|--------------------------------------------------------------------------------------------| +| `user` | [user](#user_type) | **(mandatory)** The representation of a user for your feature flag system. | + +#### Response + +With the input user the API will loop over all flags and get values for all of them. + +**Example:** +```json +{ + "flags": { + "flag-only-for-admin": { + "value": false, + "timestamp": 1655123971, + "variationType": "Default", + "trackEvents": true + }, + "new-admin-access": { + "value": false, + "timestamp": 1655123971, + "variationType": "False", + "trackEvents": true + } + }, + "valid": true +} +``` + +| Field name | Type | Description | +|---------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| `flags` | map[string]variationResult | All the flags with their results _(see [Endpoint to get all flags variations for a user](#variation_results_details) for the details of the format)_ | +| `valid` | boolean | `true` if something went wrong in the relay proxy _(flag does not exists, ...)_ and we serve the **defaultValue**. | + + + +## User type + +This type represent in your request body the information about a user. +This is based on these information that we will be able to filter which variation apply for this user. + +```json +{ + "key": "123e4567-e89b-12d3-a456-426614174000", + "anonymous": false, + "custom": { + "firstname": "John", + "lastname": "Doe", + "email": "john.doe@random.io" + } +} +``` + +| Field name | Type | Default | Description | +|-------------|------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------| +| `key` | string | **none** | **(mandatory)** Unique key of your user, it could be any string, I recommend to use UUID, email or whatever who make your user unique. | +| `anonymous` | boolean | `false` | Is it an authenticated user or not. | +| `custom` | map[string]interface{} | **empty** | This is an object where you can put everything you think is useful, you will be able to use rule based on these fields. | + diff --git a/docs/docs/rollout/_category_.json b/docs/docs/rollout/_category_.json deleted file mode 100644 index fb7e169364c..00000000000 --- a/docs/docs/rollout/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "position": 60, - "collapsible": true, - "collapsed": false -} diff --git a/docs/docs/store_file/_category_.json b/docs/docs/store_file/_category_.json new file mode 100644 index 00000000000..5a330427a1b --- /dev/null +++ b/docs/docs/store_file/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 30, + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/flag_file/custom.md b/docs/docs/store_file/custom.md similarity index 96% rename from docs/docs/flag_file/custom.md rename to docs/docs/store_file/custom.md index acf43efa7b2..dd6815d575d 100644 --- a/docs/docs/flag_file/custom.md +++ b/docs/docs/store_file/custom.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 30 +--- + # Custom Retriever To create a custom retriever you must have a `struct` that implements the [`Retriever`](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/#Retriever) interface. diff --git a/docs/docs/flag_file/file.md b/docs/docs/store_file/file.md similarity index 96% rename from docs/docs/flag_file/file.md rename to docs/docs/store_file/file.md index 1391a675717..0add1570e13 100644 --- a/docs/docs/flag_file/file.md +++ b/docs/docs/store_file/file.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 7 +--- + # File The [**File Retriever**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/fileretriever/#Retriever) will read a local file to get your flags. diff --git a/docs/docs/flag_file/github.md b/docs/docs/store_file/github.md similarity index 98% rename from docs/docs/flag_file/github.md rename to docs/docs/store_file/github.md index 8ef6d3a1d8a..d405d6eb982 100644 --- a/docs/docs/flag_file/github.md +++ b/docs/docs/store_file/github.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 6 +--- + # Github The [**Github Retriever**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/githubretriever/#Retriever) diff --git a/docs/docs/flag_file/google_cloud_storage.md b/docs/docs/store_file/google_cloud_storage.md similarity index 98% rename from docs/docs/flag_file/google_cloud_storage.md rename to docs/docs/store_file/google_cloud_storage.md index 99b3a2c3bfb..68ff4f9d171 100644 --- a/docs/docs/flag_file/google_cloud_storage.md +++ b/docs/docs/store_file/google_cloud_storage.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 5 +--- + # Google Cloud Storage The [**Google Cloud Storage Retriever**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/gcstorageretriever/#Retriever) diff --git a/docs/docs/flag_file/http.md b/docs/docs/store_file/http.md similarity index 98% rename from docs/docs/flag_file/http.md rename to docs/docs/store_file/http.md index e47bf1125a2..1fe7e3108d8 100644 --- a/docs/docs/flag_file/http.md +++ b/docs/docs/store_file/http.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 2 +--- + # HTTP endpoint The [__HTTP Retriever__](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/httpretriever/#Retriever) diff --git a/docs/docs/flag_file/index.md b/docs/docs/store_file/index.md similarity index 91% rename from docs/docs/flag_file/index.md rename to docs/docs/store_file/index.md index 221a22167da..6c6e59852be 100644 --- a/docs/docs/flag_file/index.md +++ b/docs/docs/store_file/index.md @@ -1,4 +1,8 @@ -# Store your flag file +--- +sidebar_position: 1 +--- + +# Store your feature flag file The module supports different ways of retrieving the flag file. Available retriever are: diff --git a/docs/docs/flag_file/kubernetes_configmaps.md b/docs/docs/store_file/kubernetes_configmaps.md similarity index 97% rename from docs/docs/flag_file/kubernetes_configmaps.md rename to docs/docs/store_file/kubernetes_configmaps.md index 30107832bbf..0f94c0f6b96 100644 --- a/docs/docs/flag_file/kubernetes_configmaps.md +++ b/docs/docs/store_file/kubernetes_configmaps.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 4 +--- + # Kubernetes configmaps The [**Kubernetes Retriever**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/k8sretriever/#Retriever) will access flags in a Kubernetes ConfigMap via the [Kubernetes Go client](https://github.com/kubernetes/client-go) diff --git a/docs/docs/flag_file/s3.md b/docs/docs/store_file/s3.md similarity index 97% rename from docs/docs/flag_file/s3.md rename to docs/docs/store_file/s3.md index f8551cd073d..3f40b3a0850 100644 --- a/docs/docs/flag_file/s3.md +++ b/docs/docs/store_file/s3.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 3 +--- + # S3 Bucket The [**S3Retriever**](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/retriever/s3retriever/#Retriever) will use the [aws-sdk](https://github.com/aws/aws-sdk-go) to access your flag in an S3 bucket. diff --git a/docs/package-lock.json b/docs/package-lock.json index 994d42c3478..1d3e977191b 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,8 +8,8 @@ "name": "docs-v-2", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/preset-classic": "2.1.0", + "@docusaurus/core": "^2.2.0", + "@docusaurus/preset-classic": "^2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", @@ -18,36 +18,36 @@ "react-mailchimp-subscribe": "^2.1.3" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.1.0" + "@docusaurus/module-type-aliases": "^2.2.0" }, "engines": { "node": ">=16.14" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.1.tgz", - "integrity": "sha512-eiZw+fxMzNQn01S8dA/hcCpoWCOCwcIIEUtHHdzN5TGB3IpzLbuhqFeTfh2OUhhgkE8Uo17+wH+QJ/wYyQmmzg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz", + "integrity": "sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==", "dependencies": { - "@algolia/autocomplete-shared": "1.7.1" + "@algolia/autocomplete-shared": "1.7.2" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.1.tgz", - "integrity": "sha512-pJwmIxeJCymU1M6cGujnaIYcY3QPOVYZOXhFkWVM7IxKzy272BwCvMFMyc5NpG/QmiObBxjo7myd060OeTNJXg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz", + "integrity": "sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==", "dependencies": { - "@algolia/autocomplete-shared": "1.7.1" + "@algolia/autocomplete-shared": "1.7.2" }, "peerDependencies": { - "@algolia/client-search": "^4.9.1", - "algoliasearch": "^4.9.1" + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.1.tgz", - "integrity": "sha512-eTmGVqY3GeyBTT8IWiB2K5EuURAqhnumfktAEoHxfDY2o7vg2rSnO16ZtIG0fMgt3py28Vwgq42/bVEuaQV7pg==" + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz", + "integrity": "sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==" }, "node_modules/@algolia/cache-browser-local-storage": { "version": "4.14.2", @@ -1964,18 +1964,18 @@ } }, "node_modules/@docsearch/css": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz", - "integrity": "sha512-gaP6TxxwQC+K8D6TRx5WULUWKrcbzECOPA2KCVMuI+6C7dNiGUk5yXXzVhc5sld79XKYLnO9DRTI4mjXDYkh+g==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.0.tgz", + "integrity": "sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg==" }, "node_modules/@docsearch/react": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.2.1.tgz", - "integrity": "sha512-EzTQ/y82s14IQC5XVestiK/kFFMe2aagoYFuTAIfIb/e+4FU7kSMKonRtLwsCiLQHmjvNQq+HO+33giJ5YVtaQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.0.tgz", + "integrity": "sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==", "dependencies": { - "@algolia/autocomplete-core": "1.7.1", - "@algolia/autocomplete-preset-algolia": "1.7.1", - "@docsearch/css": "3.2.1", + "@algolia/autocomplete-core": "1.7.2", + "@algolia/autocomplete-preset-algolia": "1.7.2", + "@docsearch/css": "3.3.0", "algoliasearch": "^4.0.0" }, "peerDependencies": { @@ -1996,9 +1996,9 @@ } }, "node_modules/@docusaurus/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.1.0.tgz", - "integrity": "sha512-/ZJ6xmm+VB9Izbn0/s6h6289cbPy2k4iYFwWDhjiLsVqwa/Y0YBBcXvStfaHccudUC3OfP+26hMk7UCjc50J6Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.2.0.tgz", + "integrity": "sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==", "dependencies": { "@babel/core": "^7.18.6", "@babel/generator": "^7.18.7", @@ -2010,13 +2010,13 @@ "@babel/runtime": "^7.18.6", "@babel/runtime-corejs3": "^7.18.6", "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", + "@docusaurus/cssnano-preset": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@slorber/static-site-generator-webpack-plugin": "^4.0.7", "@svgr/webpack": "^6.2.1", "autoprefixer": "^10.4.7", @@ -2084,9 +2084,9 @@ } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.1.0.tgz", - "integrity": "sha512-pRLewcgGhOies6pzsUROfmPStDRdFw+FgV5sMtLr5+4Luv2rty5+b/eSIMMetqUsmg3A9r9bcxHk9bKAKvx3zQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz", + "integrity": "sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==", "dependencies": { "cssnano-preset-advanced": "^5.3.8", "postcss": "^8.4.14", @@ -2098,9 +2098,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.1.0.tgz", - "integrity": "sha512-uuJx2T6hDBg82joFeyobywPjSOIfeq05GfyKGHThVoXuXsu1KAzMDYcjoDxarb9CoHCI/Dor8R2MoL6zII8x1Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.2.0.tgz", + "integrity": "sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.4.0" @@ -2110,14 +2110,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.1.0.tgz", - "integrity": "sha512-i97hi7hbQjsD3/8OSFhLy7dbKGH8ryjEzOfyhQIn2CFBYOY3ko0vMVEf3IY9nD3Ld7amYzsZ8153RPkcnXA+Lg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz", + "integrity": "sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==", "dependencies": { "@babel/parser": "^7.18.8", "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", "@mdx-js/mdx": "^1.6.22", "escape-html": "^1.0.3", "file-loader": "^6.2.0", @@ -2141,12 +2141,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.1.0.tgz", - "integrity": "sha512-Z8WZaK5cis3xEtyfOT817u9xgGUauT0PuuVo85ysnFRX8n7qLN1lTPCkC+aCmFm/UcV8h/W5T4NtIsst94UntQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.2.0.tgz", + "integrity": "sha512-wDGW4IHKoOr9YuJgy7uYuKWrDrSpsUSDHLZnWQYM9fN7D5EpSmYHjFruUpKWVyxLpD/Wh0rW8hYZwdjJIQUQCQ==", "dependencies": { "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "2.1.0", + "@docusaurus/types": "2.2.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2160,17 +2160,17 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.1.0.tgz", - "integrity": "sha512-xEp6jlu92HMNUmyRBEeJ4mCW1s77aAEQO4Keez94cUY/Ap7G/r0Awa6xSLff7HL0Fjg8KK1bEbDy7q9voIavdg==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz", + "integrity": "sha512-0mWBinEh0a5J2+8ZJXJXbrCk1tSTNf7Nm4tYAl5h2/xx+PvH/Bnu0V+7mMljYm/1QlDYALNIIaT/JcoZQFUN3w==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "cheerio": "^1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^10.1.0", @@ -2190,17 +2190,17 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.1.0.tgz", - "integrity": "sha512-Rup5pqXrXlKGIC4VgwvioIhGWF7E/NNSlxv+JAxRYpik8VKlWsk9ysrdHIlpX+KJUCO9irnY21kQh2814mlp/Q==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.2.0.tgz", + "integrity": "sha512-BOazBR0XjzsHE+2K1wpNxz5QZmrJgmm3+0Re0EVPYFGW8qndCWGNtXW/0lGKhecVPML8yyFeAmnUCIs7xM2wPw==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@types/react-router-config": "^5.0.6", "combine-promises": "^1.1.0", "fs-extra": "^10.1.0", @@ -2220,15 +2220,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.1.0.tgz", - "integrity": "sha512-SwZdDZRlObHNKXTnFo7W2aF6U5ZqNVI55Nw2GCBryL7oKQSLeI0lsrMlMXdzn+fS7OuBTd3MJBO1T4Zpz0i/+g==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.2.0.tgz", + "integrity": "sha512-+OTK3FQHk5WMvdelz8v19PbEbx+CNT6VSpx7nVOvMNs5yJCKvmqBJBQ2ZSxROxhVDYn+CZOlmyrC56NSXzHf6g==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "fs-extra": "^10.1.0", "tslib": "^2.4.0", "webpack": "^5.73.0" @@ -2242,13 +2242,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.1.0.tgz", - "integrity": "sha512-8wsDq3OIfiy6440KLlp/qT5uk+WRHQXIXklNHEeZcar+Of0TZxCNe2FBpv+bzb/0qcdP45ia5i5WmR5OjN6DPw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.2.0.tgz", + "integrity": "sha512-p9vOep8+7OVl6r/NREEYxf4HMAjV8JMYJ7Bos5fCFO0Wyi9AZEo0sCTliRd7R8+dlJXZEgcngSdxAUo/Q+CJow==", "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", "fs-extra": "^10.1.0", "react-json-view": "^1.21.3", "tslib": "^2.4.0" @@ -2262,15 +2262,6 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { -<<<<<<< HEAD - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.1.0.tgz", - "integrity": "sha512-4cgeqIly/wcFVbbWP03y1QJJBgH8W+Bv6AVbWnsXNOZa1yB3AO6hf3ZdeQH9x20v9T2pREogVgAH0rSoVnNsgg==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", -======= "version": "2.2.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.2.0.tgz", "integrity": "sha512-+eZVVxVeEnV5nVQJdey9ZsfyEVMls6VyWTIj8SmX0k5EbqGvnIfET+J2pYEuKQnDIHxy+syRMoRM6AHXdHYGIg==", @@ -2278,7 +2269,6 @@ "@docusaurus/core": "2.2.0", "@docusaurus/types": "2.2.0", "@docusaurus/utils-validation": "2.2.0", ->>>>>>> origin/main "tslib": "^2.4.0" }, "engines": { @@ -2289,269 +2279,35 @@ "react-dom": "^16.8.4 || ^17.0.0" } }, -<<<<<<< HEAD -======= - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/core": { + "node_modules/@docusaurus/plugin-google-gtag": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.2.0.tgz", - "integrity": "sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.2.0.tgz", + "integrity": "sha512-6SOgczP/dYdkqUMGTRqgxAS1eTp6MnJDAQMy8VCF1QKbWZmlkx4agHDexihqmYyCujTYHqDAhm1hV26EET54NQ==", "dependencies": { - "@babel/core": "^7.18.6", - "@babel/generator": "^7.18.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.18.6", - "@babel/preset-env": "^7.18.6", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@babel/runtime": "^7.18.6", - "@babel/runtime-corejs3": "^7.18.6", - "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.2.0", - "@docusaurus/logger": "2.2.0", - "@docusaurus/mdx-loader": "2.2.0", - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.2.0", - "@docusaurus/utils-common": "2.2.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", "@docusaurus/utils-validation": "2.2.0", - "@slorber/static-site-generator-webpack-plugin": "^4.0.7", - "@svgr/webpack": "^6.2.1", - "autoprefixer": "^10.4.7", - "babel-loader": "^8.2.5", - "babel-plugin-dynamic-import-node": "^2.3.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "clean-css": "^5.3.0", - "cli-table3": "^0.6.2", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.23.3", - "css-loader": "^6.7.1", - "css-minimizer-webpack-plugin": "^4.0.0", - "cssnano": "^5.1.12", - "del": "^6.1.1", - "detect-port": "^1.3.0", - "escape-html": "^1.0.3", - "eta": "^1.12.3", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "html-minifier-terser": "^6.1.0", - "html-tags": "^3.2.0", - "html-webpack-plugin": "^5.5.0", - "import-fresh": "^3.3.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.6.1", - "postcss": "^8.4.14", - "postcss-loader": "^7.0.0", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.3", - "rtl-detect": "^1.0.4", - "semver": "^7.3.7", - "serve-handler": "^6.1.3", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.3", - "tslib": "^2.4.0", - "update-notifier": "^5.1.0", - "url-loader": "^4.1.1", - "wait-on": "^6.0.1", - "webpack": "^5.73.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-dev-server": "^4.9.3", - "webpack-merge": "^5.8.0", - "webpackbar": "^5.0.2" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/cssnano-preset": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz", - "integrity": "sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==", - "dependencies": { - "cssnano-preset-advanced": "^5.3.8", - "postcss": "^8.4.14", - "postcss-sort-media-queries": "^4.2.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.14" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/logger": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.2.0.tgz", - "integrity": "sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==", - "dependencies": { - "chalk": "^4.1.2", "tslib": "^2.4.0" }, "engines": { "node": ">=16.14" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/mdx-loader": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz", - "integrity": "sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==", - "dependencies": { - "@babel/parser": "^7.18.8", - "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.2.0", - "@docusaurus/utils": "2.2.0", - "@mdx-js/mdx": "^1.6.22", - "escape-html": "^1.0.3", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "image-size": "^1.0.1", - "mdast-util-to-string": "^2.0.0", - "remark-emoji": "^2.2.0", - "stringify-object": "^3.3.0", - "tslib": "^2.4.0", - "unified": "^9.2.2", - "unist-util-visit": "^2.0.3", - "url-loader": "^4.1.1", - "webpack": "^5.73.0" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz", - "integrity": "sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.6.0", - "react-helmet-async": "^1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.73.0", - "webpack-merge": "^5.8.0" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", "react-dom": "^16.8.4 || ^17.0.0" } }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz", - "integrity": "sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==", - "dependencies": { - "@docusaurus/logger": "2.2.0", - "@svgr/webpack": "^6.2.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "github-slugger": "^1.4.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "resolve-pathname": "^3.0.0", - "shelljs": "^0.8.5", - "tslib": "^2.4.0", - "url-loader": "^4.1.1", - "webpack": "^5.73.0" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/utils-common": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.2.0.tgz", - "integrity": "sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==", - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/utils-validation": { + "node_modules/@docusaurus/plugin-sitemap": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz", - "integrity": "sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.2.0.tgz", + "integrity": "sha512-0jAmyRDN/aI265CbWZNZuQpFqiZuo+5otk2MylU9iVrz/4J7gSc+ZJ9cy4EHrEsW7PV8s1w18hIEsmcA1YgkKg==", "dependencies": { + "@docusaurus/core": "2.2.0", "@docusaurus/logger": "2.2.0", + "@docusaurus/types": "2.2.0", "@docusaurus/utils": "2.2.0", - "joi": "^17.6.0", - "js-yaml": "^4.1.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.14" - } - }, ->>>>>>> origin/main - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.1.0.tgz", - "integrity": "sha512-/3aDlv2dMoCeiX2e+DTGvvrdTA+v3cKQV3DbmfsF4ENhvc5nKV23nth04Z3Vq0Ci1ui6Sn80TkhGk/tiCMW2AA==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.1.0.tgz", - "integrity": "sha512-2Y6Br8drlrZ/jN9MwMBl0aoi9GAjpfyfMBYpaQZXimbK+e9VjYnujXlvQ4SxtM60ASDgtHIAzfVFBkSR/MwRUw==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "fs-extra": "^10.1.0", "sitemap": "^7.1.1", "tslib": "^2.4.0" @@ -2565,42 +2321,22 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.1.0.tgz", - "integrity": "sha512-NQMnaq974K4BcSMXFSJBQ5itniw6RSyW+VT+6i90kGZzTwiuKZmsp0r9lC6BYAvvVMQUNJQwrETmlu7y2XKW7w==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/plugin-debug": "2.1.0", - "@docusaurus/plugin-google-analytics": "2.1.0", - "@docusaurus/plugin-google-gtag": "2.1.0", - "@docusaurus/plugin-sitemap": "2.1.0", - "@docusaurus/theme-classic": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-search-algolia": "2.1.0", - "@docusaurus/types": "2.1.0" - }, - "engines": { - "node": ">=16.14" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" - } - }, -<<<<<<< HEAD -======= - "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-google-analytics": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.1.0.tgz", - "integrity": "sha512-4cgeqIly/wcFVbbWP03y1QJJBgH8W+Bv6AVbWnsXNOZa1yB3AO6hf3ZdeQH9x20v9T2pREogVgAH0rSoVnNsgg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.2.0.tgz", + "integrity": "sha512-yKIWPGNx7BT8v2wjFIWvYrS+nvN04W+UameSFf8lEiJk6pss0kL6SG2MRvyULiI3BDxH+tj6qe02ncpSPGwumg==", "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", - "tslib": "^2.4.0" + "@docusaurus/core": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/plugin-debug": "2.2.0", + "@docusaurus/plugin-google-analytics": "2.2.0", + "@docusaurus/plugin-google-gtag": "2.2.0", + "@docusaurus/plugin-sitemap": "2.2.0", + "@docusaurus/theme-classic": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-search-algolia": "2.2.0", + "@docusaurus/types": "2.2.0" }, "engines": { "node": ">=16.14" @@ -2610,7 +2346,6 @@ "react-dom": "^16.8.4 || ^17.0.0" } }, ->>>>>>> origin/main "node_modules/@docusaurus/react-loadable": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", @@ -2624,22 +2359,22 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.1.0.tgz", - "integrity": "sha512-xn8ZfNMsf7gaSy9+ClFnUu71o7oKgMo5noYSS1hy3svNifRTkrBp6+MReLDsmIaj3mLf2e7+JCBYKBFbaGzQng==", - "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-translations": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.2.0.tgz", + "integrity": "sha512-kjbg/qJPwZ6H1CU/i9d4l/LcFgnuzeiGgMQlt6yPqKo0SOJIBMPuz7Rnu3r/WWbZFPi//o8acclacOzmXdUUEg==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "copy-text-to-clipboard": "^3.0.1", @@ -2663,16 +2398,16 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.1.0.tgz", - "integrity": "sha512-vT1otpVPbKux90YpZUnvknsn5zvpLf+AW1W0EDcpE9up4cDrPqfsh0QoxGHFJnobE2/qftsBFC19BneN4BH8Ag==", - "dependencies": { - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/utils": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.2.0.tgz", + "integrity": "sha512-R8BnDjYoN90DCL75gP7qYQfSjyitXuP9TdzgsKDmSFPNyrdE3twtPNa2dIN+h+p/pr+PagfxwWbd6dn722A1Dw==", + "dependencies": { + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/utils": "2.2.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2691,18 +2426,18 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.1.0.tgz", - "integrity": "sha512-rNBvi35VvENhucslEeVPOtbAzBdZY/9j55gdsweGV5bYoAXy4mHB6zTGjealcB4pJ6lJY4a5g75fXXMOlUqPfg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.2.0.tgz", + "integrity": "sha512-2h38B0tqlxgR2FZ9LpAkGrpDWVdXZ7vltfmTdX+4RsDs3A7khiNsmZB+x/x6sA4+G2V2CvrsPMlsYBy5X+cY1w==", "dependencies": { "@docsearch/react": "^3.1.1", - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-translations": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "algoliasearch": "^4.13.1", "algoliasearch-helper": "^3.10.0", "clsx": "^1.2.1", @@ -2721,9 +2456,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.1.0.tgz", - "integrity": "sha512-07n2akf2nqWvtJeMy3A+7oSGMuu5F673AovXVwY0aGAux1afzGCiqIFlYW3EP0CujvDJAEFSQi/Tetfh+95JNg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.2.0.tgz", + "integrity": "sha512-3T140AG11OjJrtKlY4pMZ5BzbGRDjNs2co5hJ6uYJG1bVWlhcaFGqkaZ5lCgKflaNHD7UHBHU9Ec5f69jTdd6w==", "dependencies": { "fs-extra": "^10.1.0", "tslib": "^2.4.0" @@ -2733,9 +2468,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.1.0.tgz", - "integrity": "sha512-BS1ebpJZnGG6esKqsjtEC9U9qSaPylPwlO7cQ1GaIE7J/kMZI3FITnNn0otXXu7c7ZTqhb6+8dOrG6fZn6fqzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz", + "integrity": "sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", @@ -2752,11 +2487,11 @@ } }, "node_modules/@docusaurus/utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.1.0.tgz", - "integrity": "sha512-fPvrfmAuC54n8MjZuG4IysaMdmvN5A/qr7iFLbSGSyDrsbP4fnui6KdZZIa/YOLIPLec8vjZ8RIITJqF18mx4A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz", + "integrity": "sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==", "dependencies": { - "@docusaurus/logger": "2.1.0", + "@docusaurus/logger": "2.2.0", "@svgr/webpack": "^6.2.1", "file-loader": "^6.2.0", "fs-extra": "^10.1.0", @@ -2785,9 +2520,9 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.1.0.tgz", - "integrity": "sha512-F2vgmt4yRFgRQR2vyEFGTWeyAdmgKbtmu3sjHObF0tjjx/pN0Iw/c6eCopaH34E6tc9nO0nvp01pwW+/86d1fg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.2.0.tgz", + "integrity": "sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==", "dependencies": { "tslib": "^2.4.0" }, @@ -2804,12 +2539,12 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.1.0.tgz", - "integrity": "sha512-AMJzWYKL3b7FLltKtDXNLO9Y649V2BXvrnRdnW2AA+PpBnYV78zKLSCz135cuWwRj1ajNtP4onbXdlnyvCijGQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz", + "integrity": "sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==", "dependencies": { - "@docusaurus/logger": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", "joi": "^17.6.0", "js-yaml": "^4.1.0", "tslib": "^2.4.0" @@ -4192,9 +3927,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.12", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz", - "integrity": "sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==", + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", "funding": [ { "type": "opencollective", @@ -4207,7 +3942,7 @@ ], "dependencies": { "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001407", + "caniuse-lite": "^1.0.30001426", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -4627,9 +4362,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001419", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz", - "integrity": "sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==", + "version": "1.0.30001429", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", + "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==", "funding": [ { "type": "opencollective", @@ -5450,12 +5185,12 @@ } }, "node_modules/cssnano-preset-advanced": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.8.tgz", - "integrity": "sha512-xUlLLnEB1LjpEik+zgRNlk8Y/koBPPtONZjp7JKbXigeAmCrFvq9H0pXW5jJV45bQWAlmJ0sKy+IMr0XxLYQZg==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.9.tgz", + "integrity": "sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg==", "dependencies": { - "autoprefixer": "^10.3.7", - "cssnano-preset-default": "^5.2.12", + "autoprefixer": "^10.4.12", + "cssnano-preset-default": "^5.2.13", "postcss-discard-unused": "^5.1.0", "postcss-merge-idents": "^5.1.1", "postcss-reduce-idents": "^5.2.0", @@ -5469,24 +5204,24 @@ } }, "node_modules/cssnano-preset-default": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", - "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", + "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", "dependencies": { - "css-declaration-sorter": "^6.3.0", + "css-declaration-sorter": "^6.3.1", "cssnano-utils": "^3.1.0", "postcss-calc": "^8.2.3", "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.2", + "postcss-convert-values": "^5.1.3", "postcss-discard-comments": "^5.1.2", "postcss-discard-duplicates": "^5.1.0", "postcss-discard-empty": "^5.1.1", "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.6", - "postcss-merge-rules": "^5.1.2", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.3", "postcss-minify-font-values": "^5.1.0", "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.3", + "postcss-minify-params": "^5.1.4", "postcss-minify-selectors": "^5.2.1", "postcss-normalize-charset": "^5.1.0", "postcss-normalize-display-values": "^5.1.0", @@ -5494,11 +5229,11 @@ "postcss-normalize-repeat-style": "^5.1.1", "postcss-normalize-string": "^5.1.0", "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", "postcss-normalize-url": "^5.1.0", "postcss-normalize-whitespace": "^5.1.1", "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-initial": "^5.1.1", "postcss-reduce-transforms": "^5.1.0", "postcss-svgo": "^5.1.0", "postcss-unique-selectors": "^5.1.1" @@ -6958,9 +6693,9 @@ } }, "node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" }, "node_modules/glob": { "version": "7.2.3", @@ -9317,11 +9052,11 @@ } }, "node_modules/postcss-convert-values": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", - "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", "dependencies": { - "browserslist": "^4.20.3", + "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -9426,12 +9161,12 @@ } }, "node_modules/postcss-merge-longhand": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", - "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.0" + "stylehacks": "^5.1.1" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -9441,11 +9176,11 @@ } }, "node_modules/postcss-merge-rules": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", - "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", + "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", "dependencies": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "caniuse-api": "^3.0.0", "cssnano-utils": "^3.1.0", "postcss-selector-parser": "^6.0.5" @@ -9488,11 +9223,11 @@ } }, "node_modules/postcss-minify-params": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", - "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", "dependencies": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "cssnano-utils": "^3.1.0", "postcss-value-parser": "^4.2.0" }, @@ -9654,11 +9389,11 @@ } }, "node_modules/postcss-normalize-unicode": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", - "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", "dependencies": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -9727,11 +9462,11 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", - "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", + "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", "dependencies": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "caniuse-api": "^3.0.0" }, "engines": { @@ -11677,11 +11412,11 @@ } }, "node_modules/stylehacks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", - "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", "dependencies": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "postcss-selector-parser": "^6.0.4" }, "engines": { @@ -12094,9 +11829,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", "funding": [ { "type": "opencollective", @@ -13262,25 +12997,25 @@ }, "dependencies": { "@algolia/autocomplete-core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.1.tgz", - "integrity": "sha512-eiZw+fxMzNQn01S8dA/hcCpoWCOCwcIIEUtHHdzN5TGB3IpzLbuhqFeTfh2OUhhgkE8Uo17+wH+QJ/wYyQmmzg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz", + "integrity": "sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==", "requires": { - "@algolia/autocomplete-shared": "1.7.1" + "@algolia/autocomplete-shared": "1.7.2" } }, "@algolia/autocomplete-preset-algolia": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.1.tgz", - "integrity": "sha512-pJwmIxeJCymU1M6cGujnaIYcY3QPOVYZOXhFkWVM7IxKzy272BwCvMFMyc5NpG/QmiObBxjo7myd060OeTNJXg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz", + "integrity": "sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==", "requires": { - "@algolia/autocomplete-shared": "1.7.1" + "@algolia/autocomplete-shared": "1.7.2" } }, "@algolia/autocomplete-shared": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.1.tgz", - "integrity": "sha512-eTmGVqY3GeyBTT8IWiB2K5EuURAqhnumfktAEoHxfDY2o7vg2rSnO16ZtIG0fMgt3py28Vwgq42/bVEuaQV7pg==" + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz", + "integrity": "sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==" }, "@algolia/cache-browser-local-storage": { "version": "4.14.2", @@ -14619,25 +14354,25 @@ "optional": true }, "@docsearch/css": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz", - "integrity": "sha512-gaP6TxxwQC+K8D6TRx5WULUWKrcbzECOPA2KCVMuI+6C7dNiGUk5yXXzVhc5sld79XKYLnO9DRTI4mjXDYkh+g==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.0.tgz", + "integrity": "sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg==" }, "@docsearch/react": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.2.1.tgz", - "integrity": "sha512-EzTQ/y82s14IQC5XVestiK/kFFMe2aagoYFuTAIfIb/e+4FU7kSMKonRtLwsCiLQHmjvNQq+HO+33giJ5YVtaQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.0.tgz", + "integrity": "sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==", "requires": { - "@algolia/autocomplete-core": "1.7.1", - "@algolia/autocomplete-preset-algolia": "1.7.1", - "@docsearch/css": "3.2.1", + "@algolia/autocomplete-core": "1.7.2", + "@algolia/autocomplete-preset-algolia": "1.7.2", + "@docsearch/css": "3.3.0", "algoliasearch": "^4.0.0" } }, "@docusaurus/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.1.0.tgz", - "integrity": "sha512-/ZJ6xmm+VB9Izbn0/s6h6289cbPy2k4iYFwWDhjiLsVqwa/Y0YBBcXvStfaHccudUC3OfP+26hMk7UCjc50J6Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.2.0.tgz", + "integrity": "sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==", "requires": { "@babel/core": "^7.18.6", "@babel/generator": "^7.18.7", @@ -14649,13 +14384,13 @@ "@babel/runtime": "^7.18.6", "@babel/runtime-corejs3": "^7.18.6", "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", + "@docusaurus/cssnano-preset": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@slorber/static-site-generator-webpack-plugin": "^4.0.7", "@svgr/webpack": "^6.2.1", "autoprefixer": "^10.4.7", @@ -14713,9 +14448,9 @@ } }, "@docusaurus/cssnano-preset": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.1.0.tgz", - "integrity": "sha512-pRLewcgGhOies6pzsUROfmPStDRdFw+FgV5sMtLr5+4Luv2rty5+b/eSIMMetqUsmg3A9r9bcxHk9bKAKvx3zQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz", + "integrity": "sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==", "requires": { "cssnano-preset-advanced": "^5.3.8", "postcss": "^8.4.14", @@ -14724,23 +14459,23 @@ } }, "@docusaurus/logger": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.1.0.tgz", - "integrity": "sha512-uuJx2T6hDBg82joFeyobywPjSOIfeq05GfyKGHThVoXuXsu1KAzMDYcjoDxarb9CoHCI/Dor8R2MoL6zII8x1Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.2.0.tgz", + "integrity": "sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==", "requires": { "chalk": "^4.1.2", "tslib": "^2.4.0" } }, "@docusaurus/mdx-loader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.1.0.tgz", - "integrity": "sha512-i97hi7hbQjsD3/8OSFhLy7dbKGH8ryjEzOfyhQIn2CFBYOY3ko0vMVEf3IY9nD3Ld7amYzsZ8153RPkcnXA+Lg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz", + "integrity": "sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==", "requires": { "@babel/parser": "^7.18.8", "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", "@mdx-js/mdx": "^1.6.22", "escape-html": "^1.0.3", "file-loader": "^6.2.0", @@ -14757,12 +14492,12 @@ } }, "@docusaurus/module-type-aliases": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.1.0.tgz", - "integrity": "sha512-Z8WZaK5cis3xEtyfOT817u9xgGUauT0PuuVo85ysnFRX8n7qLN1lTPCkC+aCmFm/UcV8h/W5T4NtIsst94UntQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.2.0.tgz", + "integrity": "sha512-wDGW4IHKoOr9YuJgy7uYuKWrDrSpsUSDHLZnWQYM9fN7D5EpSmYHjFruUpKWVyxLpD/Wh0rW8hYZwdjJIQUQCQ==", "requires": { "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "2.1.0", + "@docusaurus/types": "2.2.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -14772,17 +14507,17 @@ } }, "@docusaurus/plugin-content-blog": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.1.0.tgz", - "integrity": "sha512-xEp6jlu92HMNUmyRBEeJ4mCW1s77aAEQO4Keez94cUY/Ap7G/r0Awa6xSLff7HL0Fjg8KK1bEbDy7q9voIavdg==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz", + "integrity": "sha512-0mWBinEh0a5J2+8ZJXJXbrCk1tSTNf7Nm4tYAl5h2/xx+PvH/Bnu0V+7mMljYm/1QlDYALNIIaT/JcoZQFUN3w==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "cheerio": "^1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^10.1.0", @@ -14795,17 +14530,17 @@ } }, "@docusaurus/plugin-content-docs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.1.0.tgz", - "integrity": "sha512-Rup5pqXrXlKGIC4VgwvioIhGWF7E/NNSlxv+JAxRYpik8VKlWsk9ysrdHIlpX+KJUCO9irnY21kQh2814mlp/Q==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.2.0.tgz", + "integrity": "sha512-BOazBR0XjzsHE+2K1wpNxz5QZmrJgmm3+0Re0EVPYFGW8qndCWGNtXW/0lGKhecVPML8yyFeAmnUCIs7xM2wPw==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@types/react-router-config": "^5.0.6", "combine-promises": "^1.1.0", "fs-extra": "^10.1.0", @@ -14818,44 +14553,34 @@ } }, "@docusaurus/plugin-content-pages": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.1.0.tgz", - "integrity": "sha512-SwZdDZRlObHNKXTnFo7W2aF6U5ZqNVI55Nw2GCBryL7oKQSLeI0lsrMlMXdzn+fS7OuBTd3MJBO1T4Zpz0i/+g==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.2.0.tgz", + "integrity": "sha512-+OTK3FQHk5WMvdelz8v19PbEbx+CNT6VSpx7nVOvMNs5yJCKvmqBJBQ2ZSxROxhVDYn+CZOlmyrC56NSXzHf6g==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "fs-extra": "^10.1.0", "tslib": "^2.4.0", "webpack": "^5.73.0" } }, "@docusaurus/plugin-debug": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.1.0.tgz", - "integrity": "sha512-8wsDq3OIfiy6440KLlp/qT5uk+WRHQXIXklNHEeZcar+Of0TZxCNe2FBpv+bzb/0qcdP45ia5i5WmR5OjN6DPw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.2.0.tgz", + "integrity": "sha512-p9vOep8+7OVl6r/NREEYxf4HMAjV8JMYJ7Bos5fCFO0Wyi9AZEo0sCTliRd7R8+dlJXZEgcngSdxAUo/Q+CJow==", "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", "fs-extra": "^10.1.0", "react-json-view": "^1.21.3", "tslib": "^2.4.0" } }, "@docusaurus/plugin-google-analytics": { -<<<<<<< HEAD - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.1.0.tgz", - "integrity": "sha512-4cgeqIly/wcFVbbWP03y1QJJBgH8W+Bv6AVbWnsXNOZa1yB3AO6hf3ZdeQH9x20v9T2pREogVgAH0rSoVnNsgg==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", - "tslib": "^2.4.0" -======= "version": "2.2.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.2.0.tgz", "integrity": "sha512-+eZVVxVeEnV5nVQJdey9ZsfyEVMls6VyWTIj8SmX0k5EbqGvnIfET+J2pYEuKQnDIHxy+syRMoRM6AHXdHYGIg==", @@ -14864,250 +14589,52 @@ "@docusaurus/types": "2.2.0", "@docusaurus/utils-validation": "2.2.0", "tslib": "^2.4.0" - }, - "dependencies": { - "@docusaurus/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.2.0.tgz", - "integrity": "sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==", - "requires": { - "@babel/core": "^7.18.6", - "@babel/generator": "^7.18.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.18.6", - "@babel/preset-env": "^7.18.6", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@babel/runtime": "^7.18.6", - "@babel/runtime-corejs3": "^7.18.6", - "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.2.0", - "@docusaurus/logger": "2.2.0", - "@docusaurus/mdx-loader": "2.2.0", - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.2.0", - "@docusaurus/utils-common": "2.2.0", - "@docusaurus/utils-validation": "2.2.0", - "@slorber/static-site-generator-webpack-plugin": "^4.0.7", - "@svgr/webpack": "^6.2.1", - "autoprefixer": "^10.4.7", - "babel-loader": "^8.2.5", - "babel-plugin-dynamic-import-node": "^2.3.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "clean-css": "^5.3.0", - "cli-table3": "^0.6.2", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.23.3", - "css-loader": "^6.7.1", - "css-minimizer-webpack-plugin": "^4.0.0", - "cssnano": "^5.1.12", - "del": "^6.1.1", - "detect-port": "^1.3.0", - "escape-html": "^1.0.3", - "eta": "^1.12.3", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "html-minifier-terser": "^6.1.0", - "html-tags": "^3.2.0", - "html-webpack-plugin": "^5.5.0", - "import-fresh": "^3.3.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.6.1", - "postcss": "^8.4.14", - "postcss-loader": "^7.0.0", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.3", - "rtl-detect": "^1.0.4", - "semver": "^7.3.7", - "serve-handler": "^6.1.3", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.3", - "tslib": "^2.4.0", - "update-notifier": "^5.1.0", - "url-loader": "^4.1.1", - "wait-on": "^6.0.1", - "webpack": "^5.73.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-dev-server": "^4.9.3", - "webpack-merge": "^5.8.0", - "webpackbar": "^5.0.2" - } - }, - "@docusaurus/cssnano-preset": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz", - "integrity": "sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==", - "requires": { - "cssnano-preset-advanced": "^5.3.8", - "postcss": "^8.4.14", - "postcss-sort-media-queries": "^4.2.1", - "tslib": "^2.4.0" - } - }, - "@docusaurus/logger": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.2.0.tgz", - "integrity": "sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==", - "requires": { - "chalk": "^4.1.2", - "tslib": "^2.4.0" - } - }, - "@docusaurus/mdx-loader": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz", - "integrity": "sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==", - "requires": { - "@babel/parser": "^7.18.8", - "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.2.0", - "@docusaurus/utils": "2.2.0", - "@mdx-js/mdx": "^1.6.22", - "escape-html": "^1.0.3", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "image-size": "^1.0.1", - "mdast-util-to-string": "^2.0.0", - "remark-emoji": "^2.2.0", - "stringify-object": "^3.3.0", - "tslib": "^2.4.0", - "unified": "^9.2.2", - "unist-util-visit": "^2.0.3", - "url-loader": "^4.1.1", - "webpack": "^5.73.0" - } - }, - "@docusaurus/types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz", - "integrity": "sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==", - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.6.0", - "react-helmet-async": "^1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.73.0", - "webpack-merge": "^5.8.0" - } - }, - "@docusaurus/utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz", - "integrity": "sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==", - "requires": { - "@docusaurus/logger": "2.2.0", - "@svgr/webpack": "^6.2.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.1.0", - "github-slugger": "^1.4.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "resolve-pathname": "^3.0.0", - "shelljs": "^0.8.5", - "tslib": "^2.4.0", - "url-loader": "^4.1.1", - "webpack": "^5.73.0" - } - }, - "@docusaurus/utils-common": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.2.0.tgz", - "integrity": "sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@docusaurus/utils-validation": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz", - "integrity": "sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==", - "requires": { - "@docusaurus/logger": "2.2.0", - "@docusaurus/utils": "2.2.0", - "joi": "^17.6.0", - "js-yaml": "^4.1.0", - "tslib": "^2.4.0" - } - } ->>>>>>> origin/main } }, "@docusaurus/plugin-google-gtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.1.0.tgz", - "integrity": "sha512-/3aDlv2dMoCeiX2e+DTGvvrdTA+v3cKQV3DbmfsF4ENhvc5nKV23nth04Z3Vq0Ci1ui6Sn80TkhGk/tiCMW2AA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.2.0.tgz", + "integrity": "sha512-6SOgczP/dYdkqUMGTRqgxAS1eTp6MnJDAQMy8VCF1QKbWZmlkx4agHDexihqmYyCujTYHqDAhm1hV26EET54NQ==", "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "tslib": "^2.4.0" } }, "@docusaurus/plugin-sitemap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.1.0.tgz", - "integrity": "sha512-2Y6Br8drlrZ/jN9MwMBl0aoi9GAjpfyfMBYpaQZXimbK+e9VjYnujXlvQ4SxtM60ASDgtHIAzfVFBkSR/MwRUw==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.2.0.tgz", + "integrity": "sha512-0jAmyRDN/aI265CbWZNZuQpFqiZuo+5otk2MylU9iVrz/4J7gSc+ZJ9cy4EHrEsW7PV8s1w18hIEsmcA1YgkKg==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "fs-extra": "^10.1.0", "sitemap": "^7.1.1", "tslib": "^2.4.0" } }, "@docusaurus/preset-classic": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.1.0.tgz", - "integrity": "sha512-NQMnaq974K4BcSMXFSJBQ5itniw6RSyW+VT+6i90kGZzTwiuKZmsp0r9lC6BYAvvVMQUNJQwrETmlu7y2XKW7w==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/plugin-debug": "2.1.0", - "@docusaurus/plugin-google-analytics": "2.1.0", - "@docusaurus/plugin-google-gtag": "2.1.0", - "@docusaurus/plugin-sitemap": "2.1.0", - "@docusaurus/theme-classic": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-search-algolia": "2.1.0", - "@docusaurus/types": "2.1.0" -<<<<<<< HEAD -======= - }, - "dependencies": { - "@docusaurus/plugin-google-analytics": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.1.0.tgz", - "integrity": "sha512-4cgeqIly/wcFVbbWP03y1QJJBgH8W+Bv6AVbWnsXNOZa1yB3AO6hf3ZdeQH9x20v9T2pREogVgAH0rSoVnNsgg==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", - "tslib": "^2.4.0" - } - } ->>>>>>> origin/main + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.2.0.tgz", + "integrity": "sha512-yKIWPGNx7BT8v2wjFIWvYrS+nvN04W+UameSFf8lEiJk6pss0kL6SG2MRvyULiI3BDxH+tj6qe02ncpSPGwumg==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/plugin-debug": "2.2.0", + "@docusaurus/plugin-google-analytics": "2.2.0", + "@docusaurus/plugin-google-gtag": "2.2.0", + "@docusaurus/plugin-sitemap": "2.2.0", + "@docusaurus/theme-classic": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-search-algolia": "2.2.0", + "@docusaurus/types": "2.2.0" } }, "@docusaurus/react-loadable": { @@ -15120,22 +14647,22 @@ } }, "@docusaurus/theme-classic": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.1.0.tgz", - "integrity": "sha512-xn8ZfNMsf7gaSy9+ClFnUu71o7oKgMo5noYSS1hy3svNifRTkrBp6+MReLDsmIaj3mLf2e7+JCBYKBFbaGzQng==", - "requires": { - "@docusaurus/core": "2.1.0", - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-translations": "2.1.0", - "@docusaurus/types": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-common": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.2.0.tgz", + "integrity": "sha512-kjbg/qJPwZ6H1CU/i9d4l/LcFgnuzeiGgMQlt6yPqKo0SOJIBMPuz7Rnu3r/WWbZFPi//o8acclacOzmXdUUEg==", + "requires": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "copy-text-to-clipboard": "^3.0.1", @@ -15152,16 +14679,16 @@ } }, "@docusaurus/theme-common": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.1.0.tgz", - "integrity": "sha512-vT1otpVPbKux90YpZUnvknsn5zvpLf+AW1W0EDcpE9up4cDrPqfsh0QoxGHFJnobE2/qftsBFC19BneN4BH8Ag==", - "requires": { - "@docusaurus/mdx-loader": "2.1.0", - "@docusaurus/module-type-aliases": "2.1.0", - "@docusaurus/plugin-content-blog": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/plugin-content-pages": "2.1.0", - "@docusaurus/utils": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.2.0.tgz", + "integrity": "sha512-R8BnDjYoN90DCL75gP7qYQfSjyitXuP9TdzgsKDmSFPNyrdE3twtPNa2dIN+h+p/pr+PagfxwWbd6dn722A1Dw==", + "requires": { + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/utils": "2.2.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -15173,18 +14700,18 @@ } }, "@docusaurus/theme-search-algolia": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.1.0.tgz", - "integrity": "sha512-rNBvi35VvENhucslEeVPOtbAzBdZY/9j55gdsweGV5bYoAXy4mHB6zTGjealcB4pJ6lJY4a5g75fXXMOlUqPfg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.2.0.tgz", + "integrity": "sha512-2h38B0tqlxgR2FZ9LpAkGrpDWVdXZ7vltfmTdX+4RsDs3A7khiNsmZB+x/x6sA4+G2V2CvrsPMlsYBy5X+cY1w==", "requires": { "@docsearch/react": "^3.1.1", - "@docusaurus/core": "2.1.0", - "@docusaurus/logger": "2.1.0", - "@docusaurus/plugin-content-docs": "2.1.0", - "@docusaurus/theme-common": "2.1.0", - "@docusaurus/theme-translations": "2.1.0", - "@docusaurus/utils": "2.1.0", - "@docusaurus/utils-validation": "2.1.0", + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", "algoliasearch": "^4.13.1", "algoliasearch-helper": "^3.10.0", "clsx": "^1.2.1", @@ -15196,18 +14723,18 @@ } }, "@docusaurus/theme-translations": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.1.0.tgz", - "integrity": "sha512-07n2akf2nqWvtJeMy3A+7oSGMuu5F673AovXVwY0aGAux1afzGCiqIFlYW3EP0CujvDJAEFSQi/Tetfh+95JNg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.2.0.tgz", + "integrity": "sha512-3T140AG11OjJrtKlY4pMZ5BzbGRDjNs2co5hJ6uYJG1bVWlhcaFGqkaZ5lCgKflaNHD7UHBHU9Ec5f69jTdd6w==", "requires": { "fs-extra": "^10.1.0", "tslib": "^2.4.0" } }, "@docusaurus/types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.1.0.tgz", - "integrity": "sha512-BS1ebpJZnGG6esKqsjtEC9U9qSaPylPwlO7cQ1GaIE7J/kMZI3FITnNn0otXXu7c7ZTqhb6+8dOrG6fZn6fqzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz", + "integrity": "sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==", "requires": { "@types/history": "^4.7.11", "@types/react": "*", @@ -15220,11 +14747,11 @@ } }, "@docusaurus/utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.1.0.tgz", - "integrity": "sha512-fPvrfmAuC54n8MjZuG4IysaMdmvN5A/qr7iFLbSGSyDrsbP4fnui6KdZZIa/YOLIPLec8vjZ8RIITJqF18mx4A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz", + "integrity": "sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==", "requires": { - "@docusaurus/logger": "2.1.0", + "@docusaurus/logger": "2.2.0", "@svgr/webpack": "^6.2.1", "file-loader": "^6.2.0", "fs-extra": "^10.1.0", @@ -15242,20 +14769,20 @@ } }, "@docusaurus/utils-common": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.1.0.tgz", - "integrity": "sha512-F2vgmt4yRFgRQR2vyEFGTWeyAdmgKbtmu3sjHObF0tjjx/pN0Iw/c6eCopaH34E6tc9nO0nvp01pwW+/86d1fg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.2.0.tgz", + "integrity": "sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==", "requires": { "tslib": "^2.4.0" } }, "@docusaurus/utils-validation": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.1.0.tgz", - "integrity": "sha512-AMJzWYKL3b7FLltKtDXNLO9Y649V2BXvrnRdnW2AA+PpBnYV78zKLSCz135cuWwRj1ajNtP4onbXdlnyvCijGQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz", + "integrity": "sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==", "requires": { - "@docusaurus/logger": "2.1.0", - "@docusaurus/utils": "2.1.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", "joi": "^17.6.0", "js-yaml": "^4.1.0", "tslib": "^2.4.0" @@ -16347,12 +15874,12 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, "autoprefixer": { - "version": "10.4.12", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz", - "integrity": "sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==", + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", "requires": { "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001407", + "caniuse-lite": "^1.0.30001426", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -16668,9 +16195,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001419", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz", - "integrity": "sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==" + "version": "1.0.30001429", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", + "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==" }, "ccount": { "version": "1.1.0", @@ -17221,12 +16748,12 @@ } }, "cssnano-preset-advanced": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.8.tgz", - "integrity": "sha512-xUlLLnEB1LjpEik+zgRNlk8Y/koBPPtONZjp7JKbXigeAmCrFvq9H0pXW5jJV45bQWAlmJ0sKy+IMr0XxLYQZg==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.9.tgz", + "integrity": "sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg==", "requires": { - "autoprefixer": "^10.3.7", - "cssnano-preset-default": "^5.2.12", + "autoprefixer": "^10.4.12", + "cssnano-preset-default": "^5.2.13", "postcss-discard-unused": "^5.1.0", "postcss-merge-idents": "^5.1.1", "postcss-reduce-idents": "^5.2.0", @@ -17234,24 +16761,24 @@ } }, "cssnano-preset-default": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", - "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", + "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", "requires": { - "css-declaration-sorter": "^6.3.0", + "css-declaration-sorter": "^6.3.1", "cssnano-utils": "^3.1.0", "postcss-calc": "^8.2.3", "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.2", + "postcss-convert-values": "^5.1.3", "postcss-discard-comments": "^5.1.2", "postcss-discard-duplicates": "^5.1.0", "postcss-discard-empty": "^5.1.1", "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.6", - "postcss-merge-rules": "^5.1.2", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.3", "postcss-minify-font-values": "^5.1.0", "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.3", + "postcss-minify-params": "^5.1.4", "postcss-minify-selectors": "^5.2.1", "postcss-normalize-charset": "^5.1.0", "postcss-normalize-display-values": "^5.1.0", @@ -17259,11 +16786,11 @@ "postcss-normalize-repeat-style": "^5.1.1", "postcss-normalize-string": "^5.1.0", "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", "postcss-normalize-url": "^5.1.0", "postcss-normalize-whitespace": "^5.1.1", "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-initial": "^5.1.1", "postcss-reduce-transforms": "^5.1.0", "postcss-svgo": "^5.1.0", "postcss-unique-selectors": "^5.1.1" @@ -18335,9 +17862,9 @@ } }, "github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" }, "glob": { "version": "7.2.3", @@ -20044,11 +19571,11 @@ } }, "postcss-convert-values": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", - "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", "requires": { - "browserslist": "^4.20.3", + "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" } }, @@ -20104,20 +19631,20 @@ } }, "postcss-merge-longhand": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", - "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.0" + "stylehacks": "^5.1.1" } }, "postcss-merge-rules": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", - "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", + "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "caniuse-api": "^3.0.0", "cssnano-utils": "^3.1.0", "postcss-selector-parser": "^6.0.5" @@ -20142,11 +19669,11 @@ } }, "postcss-minify-params": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", - "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "cssnano-utils": "^3.1.0", "postcss-value-parser": "^4.2.0" } @@ -20238,11 +19765,11 @@ } }, "postcss-normalize-unicode": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", - "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" } }, @@ -20281,11 +19808,11 @@ } }, "postcss-reduce-initial": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", - "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", + "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "caniuse-api": "^3.0.0" } }, @@ -21739,11 +21266,11 @@ } }, "stylehacks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", - "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", "requires": { - "browserslist": "^4.16.6", + "browserslist": "^4.21.4", "postcss-selector-parser": "^6.0.4" } }, @@ -22028,9 +21555,9 @@ "peer": true }, "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==" + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==" }, "unherit": { "version": "1.1.3", diff --git a/docs/package.json b/docs/package.json index 24d52859c3b..9ffe0267878 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,8 +14,8 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "2.1.0", - "@docusaurus/preset-classic": "2.1.0", + "@docusaurus/core": "^2.2.0", + "@docusaurus/preset-classic": "^2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", @@ -24,7 +24,7 @@ "react-mailchimp-subscribe": "^2.1.3" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.1.0" + "@docusaurus/module-type-aliases": "^2.2.0" }, "browserslist": { "production": [ diff --git a/docs/static/docs/openfeature/concepts.jpg b/docs/static/docs/openfeature/concepts.jpg new file mode 100644 index 0000000000000000000000000000000000000000..47f564cb4ebf6127a0daafddfe9985860fdec9dc GIT binary patch literal 89608 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 literal 0 HcmV?d00001 From 92f1e2cdf309fc49278c7747631850b063d49d1d Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Tue, 1 Nov 2022 23:42:42 +0100 Subject: [PATCH 2/9] More explicit text Signed-off-by: Thomas Poignant --- README.md | 4 ++-- docs/docs/openfeature_sdk/install_relay_proxy.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index af54a0c2e89..86c00c806f2 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ Originally GO Feature Flag was built to be a GOlang only library, but it limits To be compatible with more language we have implemented the [GO Feature Flag Relay Proxy](https://github.com/thomaspoignant/go-feature-flag-relay-proxy). It is a service you can host that provides an API to evaluate your flags, you can call it using HTTP to get your variation. -Since we believe in standardization we are also implementing [Open-feature](https://github.com/open-feature) providers to interact with this API in the language of your choice. -_(Open-feature is still at an early stage, so not all languages are supported and expect some changes in the future)_ +Since we believe in standardization we are also implementing [OpenFeature](https://github.com/open-feature) providers to interact with this API in the language of your choice. +_(OpenFeature is still at an early stage, so not all languages are supported and expect some changes in the future)_ ## Getting started First, you need to initialize the `ffclient` with the location of your backend file. diff --git a/docs/docs/openfeature_sdk/install_relay_proxy.md b/docs/docs/openfeature_sdk/install_relay_proxy.md index 0888dbddb2d..8aa9fe61f7e 100644 --- a/docs/docs/openfeature_sdk/install_relay_proxy.md +++ b/docs/docs/openfeature_sdk/install_relay_proxy.md @@ -1,6 +1,6 @@ --- sidebar_position: 20 -description: Relay proxy is the component that will challenge the flags, this page explain how to install it. +description: Relay proxy is the component that will evaluate the flags, this page explain how to install it. --- # Install the relay proxy From c7206931f8099e109e8af2efed6cc3485a2cd0db Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 3 Nov 2022 10:39:35 +0100 Subject: [PATCH 3/9] WIP refactor doc Signed-off-by: Thomas Poignant --- docs/docs/configure_flag/flag_format.md | 357 ++++++++++-------- docs/docs/configure_flag/rollout/canary.md | 65 +++- .../configure_flag/rollout/experimentation.md | 86 +++-- .../configure_flag/rollout/progressive.md | 101 ++--- docs/docs/configure_flag/rollout/scheduled.md | 152 +++++--- docs/docs/configure_flag/rule_format.md | 147 ++++++++ docs/docs/configure_flag/store_your_flags.md | 8 + docs/docs/getting_started/using-go-module.md | 30 +- .../go_module/data_collection/_category_.json | 2 +- docs/docs/go_module/data_collection/index.md | 10 + docs/docs/go_module/notifier/_category_.json | 2 +- .../store_file/_category_.json | 0 .../docs/{ => go_module}/store_file/custom.md | 0 docs/docs/{ => go_module}/store_file/file.md | 0 .../docs/{ => go_module}/store_file/github.md | 0 .../store_file/google_cloud_storage.md | 0 docs/docs/{ => go_module}/store_file/http.md | 0 docs/docs/{ => go_module}/store_file/index.md | 2 +- .../store_file/kubernetes_configmaps.md | 0 docs/docs/{ => go_module}/store_file/s3.md | 0 .../target_user.md | 4 +- .../openfeature_sdk/install_relay_proxy.md | 52 --- docs/docusaurus.config.js | 6 + 23 files changed, 666 insertions(+), 358 deletions(-) create mode 100644 docs/docs/configure_flag/rule_format.md create mode 100644 docs/docs/configure_flag/store_your_flags.md rename docs/docs/{ => go_module}/store_file/_category_.json (100%) rename docs/docs/{ => go_module}/store_file/custom.md (100%) rename docs/docs/{ => go_module}/store_file/file.md (100%) rename docs/docs/{ => go_module}/store_file/github.md (100%) rename docs/docs/{ => go_module}/store_file/google_cloud_storage.md (100%) rename docs/docs/{ => go_module}/store_file/http.md (100%) rename docs/docs/{ => go_module}/store_file/index.md (93%) rename docs/docs/{ => go_module}/store_file/kubernetes_configmaps.md (100%) rename docs/docs/{ => go_module}/store_file/s3.md (100%) rename docs/docs/{configure_flag => go_module}/target_user.md (98%) diff --git a/docs/docs/configure_flag/flag_format.md b/docs/docs/configure_flag/flag_format.md index 90b7b093762..52b00a4c782 100644 --- a/docs/docs/configure_flag/flag_format.md +++ b/docs/docs/configure_flag/flag_format.md @@ -1,5 +1,5 @@ --- -sidebar_position: 10 +sidebar_position: 20 description: What is a flag and how you can create them. --- @@ -14,15 +14,15 @@ Your file must be a valid `YAML`, `JSON` or `TOML` file with a list of flags :::tip The easiest way to create your configuration file is to used -[**GO Feature Flag Editor** available at https://editor.gofeatureflag.org](https://editor.gofeatureflag.org/). - +[**GO Feature Flag Editor** available at [https://editor.gofeatureflag.org](https://editor.gofeatureflag.org). + If you prefer to do it manually please follow instruction bellow. ::: ## Editor Creating the first version of the flag is not always pleasant, that's why we have created -[**GO Feature Flag Editor**](https://thomaspoignant.github.io/go-feature-flag-editor/) a simple editor to create your files. +[**GO Feature Flag Editor**](https://editor.gofeatureflag.org) a simple editor to create your files. ## Example @@ -30,176 +30,235 @@ A flag configuration looks like: ### YAML -``` yaml -test-flag: - percentage: 100 - rule: key eq "random-key" - true: true - false: false - default: false - disable: false - trackEvents: true - version: 1 - rollout: - experimentation: - start: 2021-03-20T00:00:00.10-05:00 - end: 2021-03-21T00:00:00.10-05:00 - -test-flag2: - rule: key eq "not-a-key" - percentage: 100 - true: true - false: false - default: false - version: 12 +```yaml +# This is your configuration for your first flag +first-flag: + variations: # All possible return value for your feature flag + A: false + B: true + targeting: # If you want to target a subset of your users in particular + - query: key eq "random-key" + percentage: + A: 0 + B: 100 + defaultRule: # When no targeting match we use the defaultRule + variation: A + +# A second example of a flag configuration +second-flag: + variations: + A: "valueA" + B: "valueB" + defaultValue: "a default value" + targeting: + - name: legacyRuleV0 + query: key eq "not-a-key" + percentage: + A: 10 + B: 90 + defaultRule: + name: legacyDefaultRule + variation: defaultValue + version: "12" + experimentation: + start: 2021-03-20T00:00:00.1-05:00 + end: 2021-03-21T00:00:00.1-05:00 ``` + ### JSON +
+ JSON example -``` json +```json { - "test-flag": { - "percentage": 100, - "rule": "key eq \"random-key\"", - "true": true, - "false": false, - "default": false, - "disable": false, - "trackEvents": true, - "version": 1, - "rollout": { - "experimentation": { - "start": "2021-03-20T05:00:00.100Z", - "end": "2021-03-21T05:00:00.100Z" + "first-flag": { + "variations": { + "A": false, + "B": true + }, + "targeting": [ + { + "query": "key eq \"random-key\"", + "percentage": { + "A": 0, + "B": 100 + } } + ], + "defaultRule": { + "variation": "A" } }, - "test-flag2": { - "rule": "key eq \"not-a-key\"", - "percentage": 100, - "true": true, - "false": false, - "default": false, - "version": 12 + + "second-flag": { + "variations": { + "A": "valueA", + "B": "valueB", + "defaultValue": "a default value" + }, + "targeting": [ + { + "name": "legacyRuleV0", + "query": "key eq \"not-a-key\"", + "percentage": { + "A": 10, + "B": 90 + } + } + ], + "defaultRule": { + "name": "legacyDefaultRule", + "variation": "defaultValue" + }, + "version": "12", + "experimentation": { + "start": "2021-03-20T05:00:00.100Z", + "end": "2021-03-21T05:00:00.100Z" + } } } ``` +
### TOML +
+ TOML example -``` toml -[test-flag] -percentage = 100.0 -rule = "key eq \"random-key\"" -true = true -false = false -default = false -disable = false -trackEvents = true -version = 1.0 - -[test-flag.rollout] - - [test-flag.rollout.experimentation] - start = 2021-03-20T05:00:00.100Z - end = 2021-03-21T05:00:00.100Z - -[test-flag2] -rule = "key eq \"not-a-key\"" -percentage = 100.0 -true = true -false = false -default = false -version = 12.0 -``` +```toml +[first-flag.variations] +A = false +B = true -## Format details +[[first-flag.targeting]] +query = 'key eq "random-key"' -| Field | Description | -|:-------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **flag-key** | Name of your flag.
It must be unique.
*On the example the flag keys are **`test-flag`** and **`test-flag2`**.* | -| `true` | Value returned by the flag if apply to the user *(rule is evaluated to true)* and the user is in the active percentage. | -| `false` | Value returned by the flag if apply to the user *(rule is evaluated to true)* and the user is **not** in the active percentage. | -| `default` | Value returned by the flag if not apply to the user *(rule is evaluated to false).* | -| `percentage` | *(optional)*
Percentage of the users who should be affected by the flag.
**Default: 0**

The percentage is computed by calculating a hash of the user key *(100000 variations)*, it means that you can have 3 numbers after the comma. | -| `rule` | *(optional)*
Condition to determine on which user the flag should be applied.
Rule format is described in the [rule format section](#rule-format).
**If no rule is set, the flag applies to all users *(percentage still apply)*.** | -| `disable` | *(optional)*
True if the flag is disabled.
**Default: `false`** | -| `trackEvents` | *(optional)*
False if you don't want to export the data in your data exporter.
**Default: `true`** | -| `version` | *(optional)*
The version is the version of your flag.
This number is used to display the information in the notifiers and data collection, you have to update it your self.
**Default: 0** | -| `rollout` | *(optional)*
rollout contains a specific rollout strategy you want to use.
**See [rollout section](./rollout/index.md) for more details.** | - -## Rule format - -The rule format is based on the [`nikunjy/rules`](https://github.com/nikunjy/rules) library. - -All the operations can be written capitalized or lowercase (ex: `eq` or `EQ` can be used). -Logical Operations supported are `AND` `OR`. - -Compare Expression and their definitions (`a|b` means you can use either one of the two `a` or `b`): - -| Operator | Description | -|:----------:|-----------------------------| -| `eq`, `==` | equals to | -| `ne`, `!=` | not equals to | -| `lt`, `<` | less than | -| `gt`, `>` | greater than | -| `le`, `<=` | less than equal to | -| `ge`, `>=` | greater than equal to | -| `co` | contains | -| `sw` | starts with | -| `ew` | ends with | -| `in` | in a list | -| `pr` | present | -| `not` | not of a logical expression | - -### Examples - -- Select a specific user: `key eq "example@example.com"` -- Select all identified users: `anonymous ne true` -- Select a user with a custom property: `userId eq "12345"` -- Select on multiple criteria: - *All users with ids finishing by `@test.com` that have the role `backend engineer` in the `pro` environment for the - company `go-feature-flag`* - - ```bash - (key ew "@test.com") and (role eq "backend engineer") and (env eq "pro") and (company eq "go-feature-flag")` - ``` - -## Environments - -When you initialise `go-feature-flag` you can set an environment for the instance of this SDK. - -```go linenums="1" -ffclient.Init(ffclient.Config{ - // ... - Environment: "prod", - // ... -}) -``` + [first-flag.targeting.percentage] + A = 0 + B = 100 -When an environment is set, it adds a new field in your user called **`env`** that you can use in your rules. -It means that you can decide to activate a flag only for some **environment**. +[first-flag.defaultRule] +variation = "A" -**Example of rules based on the environment:** +[second-flag] +version = "12" -```yaml -# Flag activate only in dev -rule: env == "dev" -``` + [second-flag.variations] + A = "valueA" + B = "valueB" + defaultValue = "a default value" -```yaml -# Flag used only in dev and staging environment -rule: (env == "dev") or (env == "staging") -``` + [[second-flag.targeting]] + name = "legacyRuleV0" + query = 'key eq "not-a-key"' -```yaml -# Flag used on non prod environments except for the user 1234 in prod -rule: (env != "prod") or (user_id == 1234) + [second-flag.targeting.percentage] + A = 10 + B = 90 + + [second-flag.defaultRule] + name = "legacyDefaultRule" + variation = "defaultValue" + + [second-flag.experimentation] + start = 2021-03-20T05:00:00.100Z + end = 2021-03-21T05:00:00.100Z ``` +
+ +## Format details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
flag-keyName of your flag.
It must be unique.
On the example the flag keys are test-flag and test-flag2.
variations +

Variations are all the variations available for this flag.

+

It is represented as a key/value element. The key is the name of the variation and the value could be any types available (string, float, int, map, array, bool).

+

You can have as many variation as needed.

+
# Some examples
+ variationString: test
+ variationBool: true
+ variationInt: 1000
+ variationFloat: 1000.23
+ variationArray:
- item1
- item2
+ variationObj:
item1: 123
item2: this is a string
item3: false
+
+
targeting
(optional)
+

+ Targeting contains the list of rules you have to target a subset of your users. +
You can have as many target as needed. +

+

This field is an array and contains a list of rules.

+

See rules format to have more info on how to write a rule.

+
defaultRule +

DefaultRule is the rule that is applied if the user does not match in any targeting.

+

See rules format to have more info on how to write a rule.

+
trackEvents
(optional)
+

false if you don't want to export the data in your data exporter.

+

Default: true

+
disable
(optional)
+

true if the flag is disabled.

+

Default: false

+
version
(optional)
+

The version is the version of your flag.
This string is used to display the information in the notifiers and data collection, you have to update it your self.

+

Default: ""

+
scheduledRollout
(optional)
+

Scheduled allow to patch your flag over time.

+

You can add several steps that updates the flag, this is typically used if you want to gradually add more user in your flag.

+

See Scheduled rollout to have more info on how to use it.

+
experimentation
(optional)
+

Experimentation allow you to configure a start date and an end date for your flag. When the experimentation is not running, the flag will serve the default value.

+

See Experimentation rollout to have more info on how to use it.

+
+ + + + ## Advanced configurations You can have advanced configurations for your flag to have specific behavior for them, such as: - - [Specific rollout strategies](./rollout/index.md) - [Don't track a flag](../go_module/data_collection/index.md#dont-track-a-flag) diff --git a/docs/docs/configure_flag/rollout/canary.md b/docs/docs/configure_flag/rollout/canary.md index 3a9bf20a030..89de9044ea0 100644 --- a/docs/docs/configure_flag/rollout/canary.md +++ b/docs/docs/configure_flag/rollout/canary.md @@ -9,36 +9,61 @@ You just have to select a percentage of your users in your flag, and the `True` ### YAML -``` yaml linenums="1" +```yaml canary-flag: - true: true - false: false - default: false - # highlight-next-line - percentage: 1 + variations: + oldBehavior: false + canary: true + defaultRule: + # highlight-start + percentage: + oldBehavior: 99 + canary: 1 + # highlight-end ``` ### JSON -``` json -{ +
+ JSON example + +```json + { "canary-flag": { - "true": true, - "false": false, - "default": "false, - # highlight-next-line - "percentage": 1 + "variations": { + "oldBehavior": false, + "canary": true + }, + "defaultRule": { +# highlight-start + "percentage": { + "oldBehavior": 99, + "canary": 1 + } +# highlight-end + } } } ``` +
+ + ### TOML -``` toml -[canary-flag] -true = true -false = false -default = false -# highlight-next-line -percentage = 1.0 +
+ TOML example + +```toml +[canary-flag.variations] +oldBehavior = false +canary = true + +# highlight-start +[canary-flag.defaultRule.percentage] +oldBehavior = 99 +canary = 1 +# highlight-end ``` + +
diff --git a/docs/docs/configure_flag/rollout/experimentation.md b/docs/docs/configure_flag/rollout/experimentation.md index 596d18f650e..08d10dcc093 100644 --- a/docs/docs/configure_flag/rollout/experimentation.md +++ b/docs/docs/configure_flag/rollout/experimentation.md @@ -10,58 +10,75 @@ An **experimentation rollout** is when your flag is configured to be served only ### YAML ```yaml experimentation-flag: - percentage: 50 - true: "B" - false: "A" - default: "Default" + variations: + variationA: A + variationB: B + defaultRule: + percentage: + variationA: 50 + variationB: 50 # highlight-start - rollout: - experimentation: - start: 2021-03-20T00:00:00.10-05:00 - end: 2021-03-21T00:00:00.10-05:00 + experimentation: + start: 2021-03-20T00:00:00.1-05:00 + end: 2021-03-21T00:00:00.1-05:00 # highlight-end ``` ### JSON +
+ JSON example + ```json { "experimentation-flag": { - "percentage": 50, - "true": "B", - "false": "A", - "default": "Default", - // highlight-start - "rollout": { - "experimentation": { - "start": "2021-03-20 00:00:00 -0500", - "end": "2021-03-21 00:00:00 -0500" + "variations": { + "variationA": "A", + "variationB": "B" + }, + "defaultRule": { + "percentage": { + "variationA": 50, + "variationB": 50 } }, - // highlight-end +# highlight-start + "experimentation": { + "start": "2021-03-20T05:00:00.100Z", + "end": "2021-03-21T05:00:00.100Z" + }, +# highlight-end } } ``` +
+ ### TOML +
+ TOML example + ```toml -[experimentation-flag] -percentage = 50.0 -true = "B" -false = "A" -default = "Default" +[experimentation-flag.variations] +variationA = "A" +variationB = "B" -# highlight-start -[experimentation-flag.rollout] +[experimentation-flag.defaultRule.percentage] +variationA = 50 +variationB = 50 - [experimentation-flag.rollout.experimentation] - start = 2021-03-20T05:00:00.100Z - end = 2021-03-21T05:00:00.100Z +# highlight-start +[experimentation-flag.experimentation] +start = 2021-03-20T05:00:00.100Z +end = 2021-03-21T05:00:00.100Z # highlight-end ``` - -Check this [example](https://github.com/thomaspoignant/go-feature-flag/tree/main/examples/rollout_scheduled) to see how it works. + +
+ + +Check this [example](https://github.com/thomaspoignant/go-feature-flag/tree/main/examples/rollout_experimentation) to see how it works. ## Configuration fields @@ -69,10 +86,10 @@ Check this [example](https://github.com/thomaspoignant/go-feature-flag/tree/main The dates are in the format supported natively by your flag file format. ::: -| Field | Description | -|---|---| -|**`start`**| The date the flag will be started to be served.| -|**`end`**| The date the flag will be stopped to be served.| +| Field | Description | +|-------------|-------------------------------------------------| +| **`start`** | The date the flag will be started to be served. | +| **`end`** | The date the flag will be stopped to be served. | ## A/B testing @@ -83,6 +100,7 @@ A/B tests are widely considered the simplest form of controlled experiment. _**(source wikipedia)**_ ::: + To have a proper A/B testing solution with the module you should use the experimentation rollout combined with the [export of your data](../../go_module/data_collection/). This combination will allow to have your experimentation running for a dedicated time, and you will have the data to knows exactly which user was on which version of the flag. diff --git a/docs/docs/configure_flag/rollout/progressive.md b/docs/docs/configure_flag/rollout/progressive.md index 012b730733d..7ccbc044fc9 100644 --- a/docs/docs/configure_flag/rollout/progressive.md +++ b/docs/docs/configure_flag/rollout/progressive.md @@ -2,76 +2,89 @@ A **progressive rollout** allows you to increase the percentage of your flag over time. -You can select a **release ramp** where the percentage of your flag will increase progressively between the start date and the end date. +You can select a **release ramp** where the percentage of your flag will increase progressively between the start date +and the end date. ## Example ### YAML -``` yaml +```yaml progressive-flag: - true: "B" - false: "A" - default: "Default" - # highlight-start - rollout: - progressive: - percentage: - initial: 0 - end: 100 - releaseRamp: - start: 2021-03-20T00:00:00.10-05:00 - end: 2021-03-21T00:00:00.10-05:00 - # highlight-end + variations: + variationA: A + variationB: B + defaultRule: +# highlight-start + progressiveRollout: + initial: + variation: variationB + percentage: 0 + date: 2021-03-20T00:00:00.1-05:00 + end: + variation: variationB + percentage: 100 + date: 2021-03-21T00:00:00.1-05:00 +# highlight-end ``` ### JSON -``` json +
+ JSON example + +```json { "progressive-flag": { - "true": "B", - "false": "A", - "default": "Default", - # highlight-start - "rollout": { - "progressive": { - "percentage": { - "initial": 0, - "end": 100 + "variations": { + "variationA": "A", + "variationB": "B" + }, + "defaultRule": { +# highlight-start + "progressiveRollout": { + "initial": { + "variation": "variationB", + "percentage": 0, + "date": "2021-03-20T05:00:00.100Z" }, - "releaseRamp": { - "start": "2021-03-20T05:00:00.100Z", - "end": "2021-03-21T05:00:00.100Z" + "end": { + "variation": "variationB", + "percentage": 100, + "date": "2021-03-21T05:00:00.100Z" } - } + }, +# highlight-end } - # highlight-end } } ``` +
+ ### TOML -``` toml -[progressive-flag] -true = "B" -false = "A" -default = "Default" -# highlight-start - [progressive-flag.rollout] - [progressive-flag.rollout.progressive] +
+ TOML example - [progressive-flag.rollout.progressive.percentage] - initial = 0.0 - end = 100.0 +```toml +[progressive-flag.variations] +variationA = "A" +variationB = "B" +# highlight-start +[progressive-flag.defaultRule.progressiveRollout.initial] +variation = "variationB" +percentage = 0 +date = 2021-03-20T05:00:00.100Z - [progressive-flag.rollout.progressive.releaseRamp] - start = 2021-03-20T05:00:00.100Z - end = 2021-03-21T05:00:00.100Z +[progressive-flag.defaultRule.progressiveRollout.end] +variation = "variationB" +percentage = 100 +date = 2021-03-21T05:00:00.100Z # highlight-end ``` +
## Configuration fields diff --git a/docs/docs/configure_flag/rollout/scheduled.md b/docs/docs/configure_flag/rollout/scheduled.md index 089c5eba73f..2fc3481f002 100644 --- a/docs/docs/configure_flag/rollout/scheduled.md +++ b/docs/docs/configure_flag/rollout/scheduled.md @@ -6,77 +6,127 @@ While this sounds deceptively straightforward, it unlocks the potential for user For example, you may want to turn a feature ON for internal testing tomorrow and then enable it for your ‘beta’ user segment four days later. ## Example + ### YAML -```yaml linenums="1" hl_lines="6-13" +```yaml scheduled-flag: - true: "B" - false: "A" - default: "Default" - # highlight-start - rollout: - scheduled: - steps: - - date: 2020-04-10T00:00:00.10+02:00 - rule: beta eq "true" - percentage: 100 - - - date: 2022-05-12T15:36:00.10+02:00 - rule: beta eq "false" - # highlight-end + variations: + variationA: A + variationB: B + defaultRule: + name: legacyDefaultRule + percentage: + variationA: 100 + variationB: 0 +# highlight-start + scheduledRollout: + - date: 2020-04-10T00:00:00.1+02:00 + targeting: + - name: legacyRuleV0 + query: beta eq "true" + percentage: + variationA: 0 + variationB: 100 + + - date: 2022-05-12T15:36:00.1+02:00 + targeting: + - name: legacyRuleV0 + query: beta eq "false" +# highlight-end ``` + ### JSON +
+ JSON example + ```json { "scheduled-flag": { - "true": "B", - "false": "A", - "default": "Default", - # highlight-start - "rollout": { - "scheduled": { - "steps": [ + "variations": { + "variationA": "A", + "variationB": "B" + }, + "defaultRule": { + "name": "legacyDefaultRule", + "percentage": { + "variationA": 100, + "variationB": 0 + } + }, +# highlight-start + "scheduledRollout": [ + { + "date": "2020-04-09T22:00:00.100Z", + "targeting": [ { - "date": "2020-04-09T22:00:00.100Z", - "rule": "beta eq \"true\"", - "percentage": 100 - }, + "name": "legacyRuleV0", + "query": "beta eq \"true\"", + "percentage": { + "variationA": 0, + "variationB": 100 + } + } + ] + }, + { + "date": "2022-05-12T13:36:00.100Z", + "targeting": [ { - "date": "2022-05-12T13:36:00.100Z", - "rule": "beta eq \"false\"" + "name": "legacyRuleV0", + "query": "beta eq \"false\"" } ] } - } - # highlight-end + ], +# highlight-end } } ``` +
+ ### TOML +
+ TOML example + ```toml -[scheduled-flag] -true = "B" -false = "A" -default = "Default" - # highlight-start - [scheduled-flag.rollout] - - [scheduled-flag.rollout.scheduled] - - [[scheduled-flag.rollout.scheduled.steps]] - date = 2020-04-09T22:00:00.100Z - rule = "beta eq \"true\"" - percentage = 100.0 - - [[scheduled-flag.rollout.scheduled.steps]] - date = 2022-05-12T13:36:00.100Z - rule = "beta eq \"false\"" - # highlight-end +[scheduled-flag.variations] +variationA = "A" +variationB = "B" + +[scheduled-flag.defaultRule] +name = "legacyDefaultRule" + + [scheduled-flag.defaultRule.percentage] + variationA = 100 + variationB = 0 + +# highlight-start +[[scheduled-flag.scheduledRollout]] +date = 2020-04-09T22:00:00.100Z + + [[scheduled-flag.scheduledRollout.targeting]] + name = "legacyRuleV0" + query = 'beta eq "true"' + + [scheduled-flag.scheduledRollout.targeting.percentage] + variationA = 0 + variationB = 100 + +[[scheduled-flag.scheduledRollout]] +date = 2022-05-12T13:36:00.100Z + + [[scheduled-flag.scheduledRollout.targeting]] + name = "legacyRuleV0" + query = 'beta eq "false"' +# highlight-end ``` +
+ ## Configuration fields :::info @@ -84,6 +134,6 @@ You can change any fields that are available on your flag. Since your configuration has not been changed manually, it does not trigger any notifier. ::: -| Field | Description | -|---|---| -|**`steps`**| The only mandatory field in a **step** is the `date`.
**If no date is provided the step will be skipped.**

The other attributes of your `step` are what you want to update your flag, so every field available in the [flag format](../flag_format) can be updated.
The new value in a field will override the existing one. | +| Field | Description | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`steps`** | The only mandatory field in a **step** is the `date`.
**If no date is provided the step will be skipped.**

The other attributes of your `step` are what you want to update your flag, so every field available in the [flag format](../../flag_format) can be updated.
The new value in a field will override the existing one. | diff --git a/docs/docs/configure_flag/rule_format.md b/docs/docs/configure_flag/rule_format.md new file mode 100644 index 00000000000..73236e491a0 --- /dev/null +++ b/docs/docs/configure_flag/rule_format.md @@ -0,0 +1,147 @@ +--- +sidebar_position: 21 +description: How to create a rule to target specific users +--- + +# How to target specific users + +## Rule format + +A rule is a configuration that allows to serve a variation based on some conditions. + +### Format details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
name
(optional)
Name of your rule.
This is needed when your are updating a rule using a scheduled rollout.
query +

+ Query represents an antlr query in the nikunjy/rules format. +
This field is mandatory in every rule used in the targeting field. +

+

See query format to have the syntax.

+

Note: if you use the field query in a defaultRule it will be ignored.

+
variation
(optional)
Name of the variation to return.
percentage
(optional)
+

Represents the percentage we should give to each variation.

+
+            percentage:
variationA: 10.59
variationB: 9.41
variationC: 80 +
+

The format is the name of the variation and the percentage for this one.

+

Note: if your total is not equals to 100% this rule will be considered as invalid.

+
progressiveRollout
(optional)
+

Allow to ramp up the percentage of your flag over time.

+

You can decide at which percentage you starts with and at what percentage you ends with in your release ramp. + Before the start date we will serve the initial percentage and, after we will serve the end percentage. +

+

See progressive rollout to have more info on how to use it.

+
disable
(optional)
+

Set to true if you want to disable the rule.

+

Default: true.

+
+ + +:::info +`variation`, `percentage` and `progressiveRollout` are optional but you **must have one of the 3**. + +If you have more than one field we will use the first one in that order +`progressiveRollout` > `percentage` > `variation`. +::: + +### Query format + +The rule format is based on the [`nikunjy/rules`](https://github.com/nikunjy/rules) library. + +All the operations can be written capitalized or lowercase (ex: `eq` or `EQ` can be used). +Logical Operations supported are `AND` `OR`. + +Compare Expression and their definitions (`a|b` means you can use either one of the two `a` or `b`): + +| Operator | Description | +|:----------:|-----------------------------| +| `eq`, `==` | equals to | +| `ne`, `!=` | not equals to | +| `lt`, `<` | less than | +| `gt`, `>` | greater than | +| `le`, `<=` | less than equal to | +| `ge`, `>=` | greater than equal to | +| `co` | contains | +| `sw` | starts with | +| `ew` | ends with | +| `in` | in a list | +| `pr` | present | +| `not` | not of a logical expression | + +#### Examples + +- Select a specific user: `key eq "example@example.com"` +- Select all identified users: `anonymous ne true` +- Select a user with a custom property: `userId eq "12345"` +- Select on multiple criteria: + *All users with ids finishing by `@test.com` that have the role `backend engineer` in the `pro` environment for the + company `go-feature-flag`* + + ```bash + (key ew "@test.com") and (role eq "backend engineer") and (env eq "pro") and (company eq "go-feature-flag") + ``` + +## Environments + +When you initialise `go-feature-flag` you can set an [environment](../configuration/#option_environment) for the instance of this SDK. + +```go linenums="1" +ffclient.Init(ffclient.Config{ + // ... + Environment: "prod", + // ... +}) +``` + +When an environment is set, it adds a new field in your user called **`env`** that you can use in your rules. +It means that you can decide to activate a flag only for some **environment**. + +**Example of rules based on the environment:** + +```yaml +# Flag activate only in dev +rule: env == "dev" +``` + +```yaml +# Flag used only in dev and staging environment +rule: (env == "dev") or (env == "staging") +``` + +```yaml +# Flag used on non prod environments except for the user 1234 in prod +rule: (env != "prod") or (user_id == 1234) +``` diff --git a/docs/docs/configure_flag/store_your_flags.md b/docs/docs/configure_flag/store_your_flags.md new file mode 100644 index 00000000000..fd34253af7d --- /dev/null +++ b/docs/docs/configure_flag/store_your_flags.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 10 +description: Where to store your configuration flag? +--- + +# Where to store your configuration flag + +TODO diff --git a/docs/docs/getting_started/using-go-module.md b/docs/docs/getting_started/using-go-module.md index 941416b6d78..2ed9f7c2f1f 100644 --- a/docs/docs/getting_started/using-go-module.md +++ b/docs/docs/getting_started/using-go-module.md @@ -9,20 +9,44 @@ description: Use the module in your GO application with nothing to install. go get github.com/thomaspoignant/go-feature-flag ``` +## Create a feature flag configuration + +Create a new `YAML` file containing your first flag configuration. + +```yaml title="flag-config.yaml" +# 20% of the users will use the variation "my-new-feature" +test-flag: + variations: + my-new-feature: true + my-old-feature: false + defaultRule: + percentage: + my-new-feature: 20 + my-old-feature: 80 +``` + +This flag split the usage of this flag, 20% will use the variation `my-new-feature` and 80% the variation `my-old-feature`. + ## SDK Initialisation First, you need to initialize the `ffclient` with the location of your backend file. ```go linenums="1" err := ffclient.Init(ffclient.Config{ PollingInterval: 3 * time.Second, - Retriever: &httpretriever.Retriever{ - URL: "http://example.com/flag-config.yaml", + Retriever: &fileretriever.Retriever{ + Path: "flag-config.yaml", }, }) defer ffclient.Close() ``` -*This example will load a file from an HTTP endpoint and will refresh the flags every 3 seconds (if you omit the +*This example will load a file from your local computer and will refresh the flags every 3 seconds (if you omit the PollingInterval, the default value is 60 seconds).* +:::tip +This is a basic configuration to test locally, in production it is better to use a remote place to store your feature flag configuration file. + +Look at the list of available options in the [**Store your feature flag file** page](../go_module/store_file/). +::: + ## Evaluate your flags Now you can evaluate your flags anywhere in your code. diff --git a/docs/docs/go_module/data_collection/_category_.json b/docs/docs/go_module/data_collection/_category_.json index 5a330427a1b..31a74b81af7 100644 --- a/docs/docs/go_module/data_collection/_category_.json +++ b/docs/docs/go_module/data_collection/_category_.json @@ -1,5 +1,5 @@ { - "position": 30, + "position": 40, "collapsible": true, "collapsed": true } diff --git a/docs/docs/go_module/data_collection/index.md b/docs/docs/go_module/data_collection/index.md index d53d85fac86..6988fe63546 100644 --- a/docs/docs/go_module/data_collection/index.md +++ b/docs/docs/go_module/data_collection/index.md @@ -102,6 +102,9 @@ test-flag: ### JSON +
+ JSON example + ```json { "test-flag": { @@ -115,8 +118,13 @@ test-flag: } ``` +
+ ### TOML +
+ TOML example + ```toml [test-flag] percentage = 50.0 @@ -126,3 +134,5 @@ default = "Default" # highlight-next-line trackEvents = false ``` + +
diff --git a/docs/docs/go_module/notifier/_category_.json b/docs/docs/go_module/notifier/_category_.json index 5a330427a1b..69bc4e31aa2 100644 --- a/docs/docs/go_module/notifier/_category_.json +++ b/docs/docs/go_module/notifier/_category_.json @@ -1,5 +1,5 @@ { - "position": 30, + "position": 50, "collapsible": true, "collapsed": true } diff --git a/docs/docs/store_file/_category_.json b/docs/docs/go_module/store_file/_category_.json similarity index 100% rename from docs/docs/store_file/_category_.json rename to docs/docs/go_module/store_file/_category_.json diff --git a/docs/docs/store_file/custom.md b/docs/docs/go_module/store_file/custom.md similarity index 100% rename from docs/docs/store_file/custom.md rename to docs/docs/go_module/store_file/custom.md diff --git a/docs/docs/store_file/file.md b/docs/docs/go_module/store_file/file.md similarity index 100% rename from docs/docs/store_file/file.md rename to docs/docs/go_module/store_file/file.md diff --git a/docs/docs/store_file/github.md b/docs/docs/go_module/store_file/github.md similarity index 100% rename from docs/docs/store_file/github.md rename to docs/docs/go_module/store_file/github.md diff --git a/docs/docs/store_file/google_cloud_storage.md b/docs/docs/go_module/store_file/google_cloud_storage.md similarity index 100% rename from docs/docs/store_file/google_cloud_storage.md rename to docs/docs/go_module/store_file/google_cloud_storage.md diff --git a/docs/docs/store_file/http.md b/docs/docs/go_module/store_file/http.md similarity index 100% rename from docs/docs/store_file/http.md rename to docs/docs/go_module/store_file/http.md diff --git a/docs/docs/store_file/index.md b/docs/docs/go_module/store_file/index.md similarity index 93% rename from docs/docs/store_file/index.md rename to docs/docs/go_module/store_file/index.md index 6c6e59852be..b5c52890912 100644 --- a/docs/docs/store_file/index.md +++ b/docs/docs/go_module/store_file/index.md @@ -2,7 +2,7 @@ sidebar_position: 1 --- -# Store your feature flag file +# Retrieve your feature flags configuration The module supports different ways of retrieving the flag file. Available retriever are: diff --git a/docs/docs/store_file/kubernetes_configmaps.md b/docs/docs/go_module/store_file/kubernetes_configmaps.md similarity index 100% rename from docs/docs/store_file/kubernetes_configmaps.md rename to docs/docs/go_module/store_file/kubernetes_configmaps.md diff --git a/docs/docs/store_file/s3.md b/docs/docs/go_module/store_file/s3.md similarity index 100% rename from docs/docs/store_file/s3.md rename to docs/docs/go_module/store_file/s3.md diff --git a/docs/docs/configure_flag/target_user.md b/docs/docs/go_module/target_user.md similarity index 98% rename from docs/docs/configure_flag/target_user.md rename to docs/docs/go_module/target_user.md index 237a93a6b3a..cf587ac80f9 100644 --- a/docs/docs/configure_flag/target_user.md +++ b/docs/docs/go_module/target_user.md @@ -2,10 +2,10 @@ sidebar_position: 20 description: How to select who should have the flag activated. --- -# Target a user with a flag +# Performing flag evaluations ## Users -Feature flag targeting and rollouts are all determined by the user you pass to your Variation calls. +Feature flag targeting and rollouts are all determined by the user you pass to your **Variation** calls. The SDK defines a [`User`](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/ffuser#User) struct and a [`UserBuilder`](https://pkg.go.dev/github.com/thomaspoignant/go-feature-flag/ffuser#UserBuilder) to make this easy. Here's an example: diff --git a/docs/docs/openfeature_sdk/install_relay_proxy.md b/docs/docs/openfeature_sdk/install_relay_proxy.md index 8aa9fe61f7e..8b6ab8a88f4 100644 --- a/docs/docs/openfeature_sdk/install_relay_proxy.md +++ b/docs/docs/openfeature_sdk/install_relay_proxy.md @@ -30,55 +30,3 @@ docker pull thomaspoignant/go-feature-flag-relay-proxy:latest :::info More info in the [dockerhub page](https://hub.docker.com/r/thomaspoignant/go-feature-flag-relay-proxy). ::: - -## Getting started - -Before starting your **relay proxy** you will need to create a minimal configuration file. - -```yaml -# this is a minimal config containing only where your flag file is located -retriever: - kind: http - url: https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/examples/file/flags.yaml -``` - -After that you can launch the **relay proxy** by using this command: -```shell -go-feature-flag-relay-proxy --config=/path/to/your/configfile -``` - -The **relay proxy** will read the configuration file and retrieve all the flags. -After that you can use all the available endpoints _(see **Service endpoints** section)_ and get the variations for your users. - - -## Deployment options - -A common way to run **go-feature-flag relay proxy** is to use the Docker Container. -An image is available on docker Hub [`thomaspoignant/go-feature-flag-relay-proxy`](https://hub.docker.com/r/thomaspoignant/go-feature-flag-relay-proxy). - -You can also run it as a service in your application following the **Installation** section. - -## Specifying a configuration - -To configure the relay proxy you should provide a configuration file when launching the instance. - -The easiest way to provide the file is to use the option `--config=/path_to_your_file.yaml`. -But if you don't provide this option, the relay proxy will look in these folders if a file named `goff-proxy.yaml` is available. - -- **current folder** -- `/goff/` -- `/etc/opt/goff/` - -To learn how to configure the relay proxy, read [Configuration](../go_module/configuration). - -## Exporting metrics and traces - -To export the data you can use all the capabilities of `go-feature-flag` SDK. -To configure it please refer to the [type `exporter` section](../go_module/configuration#exporter) of the configuration. - -## Service endpoints -The Relay Proxy defines many HTTP/HTTPS endpoints. -Most of these are proxies for GO Feature Flag services, to be used by SDKs that connect to the Relay Proxy. -Others are specific to the Relay Proxy, such as for monitoring its status. - -Please refer to [endpoints documentation](./relay_proxy_endpoints) to get the full details of what exists in our REST API. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 2b523eb829d..2ea1fc6954a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -91,6 +91,12 @@ const config = { }, {to: '/blog', label: 'Blog', position: 'left'}, {to: 'https://editor.gofeatureflag.org', html: 'Flag Editor ', position: 'left'}, + // { + // type: 'doc', + // docId:'migrate_v0_v1', + // label: 'v1.x.x migration', + // position: 'left' + // }, {to: 'https://github.com/sponsors/thomaspoignant', label: 'Sponsor us ❤️', position: 'right'}, {type: 'docsVersionDropdown', position: 'right'}, { From 84e92e01206579caec9200f69a6e27603570e2b2 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 3 Nov 2022 15:35:58 +0100 Subject: [PATCH 4/9] Rework doc Signed-off-by: Thomas Poignant --- docs/docs/configure_flag/store_your_flags.md | 70 ++++++- docs/docs/go_module/store_file/custom.md | 5 +- .../openfeature_sdk/configure_relay_proxy.md | 2 +- docs/docs/relay_proxy/_category_.json | 10 + .../docs/relay_proxy/configure_relay_proxy.md | 177 ++++++++++++++++++ retriever/retriever.go | 2 - 6 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 docs/docs/relay_proxy/_category_.json create mode 100644 docs/docs/relay_proxy/configure_relay_proxy.md diff --git a/docs/docs/configure_flag/store_your_flags.md b/docs/docs/configure_flag/store_your_flags.md index fd34253af7d..a62317c8f34 100644 --- a/docs/docs/configure_flag/store_your_flags.md +++ b/docs/docs/configure_flag/store_your_flags.md @@ -5,4 +5,72 @@ description: Where to store your configuration flag? # Where to store your configuration flag -TODO +To work **GO Feature Flag** is using a configuration file to store your feature flags configuration. + +Ideally this file should be placed somewhere accessible by all your app that are using the GO Feature Flag module. +In the solution there is a system we call `retriever` that is in charge of reading the file remotely. + +GO Feature Flag supports different ways to retrieve the configuration file _(see list bellow)_. + +## AWS S3 + +**AWS S3** is the object store of AWS, you can add your GO Feature Flag configuration file in any bucket and reference it in your configuration. + +- [Configure the GO module](../go_module/store_file/s3.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#s3) + +## Google Cloud Storage + +**Google Cloud Storage** is a RESTful online file storage web service for storing and accessing data on Google Cloud Platform infrastructure. +You can add your GO Feature Flag configuration file in any bucket and reference it. + +- [Configure the GO module](../go_module/store_file/google_cloud_storage.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#google-storage) + +## Kubernetes Configmaps + +A **ConfigMap** is an API object used to store non-confidential data in key-value pairs inside kubernetes. +GO Feature Flag can read directly in a `configmap` in your namespace. + +When your feature flags file is ready you can store it directly in your kubernetes instance by using this command: + +```shell +kubectl create configmap goff --from-file=examples/retriever_configmap/flags.yaml +``` + +It will allow your file to be available inside Kubernetes. + +- [Configure the GO module](../go_module/store_file/kubernetes_configmaps.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#kubernetes-configmap) + +## HTTP + +Serving file with an **HTTP** server is probably something you are already doing, **GO Feature Flag** can retrieve your configuration file, from +any HTTP endpoint and read it from there. + +- [Configure the GO module](../go_module/store_file/http.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#http) + +## GitHub + +Reading the file from **GitHub** is pretty straight forward. +You commit your file into your favorite repository _(it can be public or private)_ and **GO Feature Flag** can retrieve your configuration file and use it. + +- [Configure the GO module](../go_module/store_file/github.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#github) + +## Local file + +You can store your feature flags configuration in your hard drive directly. + +:::tip +Using a file is great for local testing, but in production it is recommended to use a distributed system instead. +::: + +- [Configure the GO module](../go_module/store_file/file.md) +- [Configure the relay proxy](../relay_proxy/configure_relay_proxy#file) + +## Custom + +If you are using the **GO module**, you can also implement your own retriever. +For this look at this [documentation](../go_module/store_file/custom.md) to start building your own `retriever`. diff --git a/docs/docs/go_module/store_file/custom.md b/docs/docs/go_module/store_file/custom.md index dd6815d575d..6ccd49273e6 100644 --- a/docs/docs/go_module/store_file/custom.md +++ b/docs/docs/go_module/store_file/custom.md @@ -12,10 +12,7 @@ type Retriever interface { } ``` -The `Retrieve` function is supposed to load the file and to return a []byte of your flag configuration file. - -If you want to specify the format of the file, you can use the `ffclient.Config.FileFormat` option to specify if it is -a `YAML`, `JSON` or `TOML` file. +The `Retrieve` function is supposed to load the file and to return a `[]byte` of your flag configuration file. You can check existing `Retriever` *([file](https://github.com/thomaspoignant/go-feature-flag/blob/main/retriever/fileretriever/retriever.go), [s3](https://github.com/thomaspoignant/go-feature-flag/blob/main/retriever/s3retriever/retriever.go), ...)* to have an idea on how to do build your own. diff --git a/docs/docs/openfeature_sdk/configure_relay_proxy.md b/docs/docs/openfeature_sdk/configure_relay_proxy.md index aa8ecc6b3bd..c8befe9a609 100644 --- a/docs/docs/openfeature_sdk/configure_relay_proxy.md +++ b/docs/docs/openfeature_sdk/configure_relay_proxy.md @@ -5,7 +5,7 @@ description: How to configure the relay proxy to serve your feature flags. # Configure the relay proxy -## Global configurationuse +## Global configuration The configuration of the **relay proxy** is based on a configuration file that you have to provide. | Field name | Type | Default | Description | diff --git a/docs/docs/relay_proxy/_category_.json b/docs/docs/relay_proxy/_category_.json new file mode 100644 index 00000000000..936077f1ef0 --- /dev/null +++ b/docs/docs/relay_proxy/_category_.json @@ -0,0 +1,10 @@ +{ + "position": 50, + "label":"Relay proxy", + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index", + "title": "Relay proxy" + } +} diff --git a/docs/docs/relay_proxy/configure_relay_proxy.md b/docs/docs/relay_proxy/configure_relay_proxy.md new file mode 100644 index 00000000000..c8befe9a609 --- /dev/null +++ b/docs/docs/relay_proxy/configure_relay_proxy.md @@ -0,0 +1,177 @@ +--- +sidebar_position: 30 +description: How to configure the relay proxy to serve your feature flags. +--- + +# Configure the relay proxy + +## Global configuration +The configuration of the **relay proxy** is based on a configuration file that you have to provide. + +| Field name | Type | Default | Description | +|---------------------------|-------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `retriever` | [retriever](#retriever) | **none** | **(mandatory)** This is the configuration on how to retrieve the configuration of the files | +| `listen` | int | `1031` | This is the port used by the relay proxy when starting the HTTP server. | +| `pollingInterval` | int | `60000` | This is the time interval **in millisecond** when the relay proxy is reloading the configuration file.
The minimum time accepted is 1000 millisecond. | +| `hideBanner` | boolean | `false` | Should we display the beautiful **go-feature-flag** banner when starting the relay proxy | +| `enableSwagger` | boolean | `false` | Do you want to enable swagger to test the APIs directly. If you are enabling Swagger you will have to provide the `host` configuration and the Swagger UI will be available at `http://:/swagger/`. | +| `host` | string | `localhost` | This is the DNS you will use to access the relay proxy. This field is used by Swagger to query the API at the right place. | +| `restApiTimeout` | int | `5000` | Timeout in millisecond we are accepting to wait in our APIs. | +| `debug` | boolean | `false` | If `true` you will have more logs in the output that will help you to better understand what happen. If an error happen in the API the error will be also shown in the body. | +| `fileFormat` | string | `yaml` | This is the format of your `go-feature-flag` configuration file. Acceptable values are `yaml`, `json`, `toml`. | +| `startWithRetrieverError` | boolean | `false` | By default the **relay proxy** will crash if he is not able to retrieve the flags from the configuration.
If you don't want your relay proxy to crash, you can set `startWithRetrieverError` to true. Until the flag is retrievable the relay proxy will only answer with default values. | +| `exporter` | [exporter](#exporter) | **none** | Exporter is the configuration on how to export data. | +| `notifier` | [notifier](#notifier) | **none** | Notifiers is the configuration on where to notify a flag change. | + + + + +## type `retriver` + +`go-feature-flag` is supporting different kind of retriever such as S3, Google store, etc ... +In this section we will present all the available retriever configuration available. + +### S3 + +| Field name | Type | Default | Description | +|------------|--------|----------|--------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** This is the name of your S3 bucket _(ex: `my-featureflag-bucket`)_. | +| `item` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | + + +### GitHub + +| Field name | Type | Default | Description | +|------------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`github`**.
_This field is mandatory and describe which retriever you are using._ | +| `repositorySlug` | string | **none** | **(mandatory)** The repository slug of the GitHub repository where your file is located _(ex: `thomaspoignant/go-feature-flag`)_. | +| `path` | string | **none** | **(mandatory)** Path to the file inside the repository _(ex: `config/flag/my-flags.yaml`)_. | +| `branch` | string | `main` | The branch we should check in the repository. | +| `githubToken` | string | **none** | Github token is used to access a private repository, you need the repo permission ([how to create a GitHub token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)). | +| `timeout` | string | `10000` | Timeout in millisecond used when calling GitHub. | + +### File + +| Field name | Type | Default | Description | +|------------|--------|----------|----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | +| `path` | string | **none** | **(mandatory)** Path to the file in your local computer _(ex: `/goff/my-flags.yaml`)_. | + + +### HTTP + +| Field name | Type | Default | Description | +|------------|---------------------|----------|----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`http`**.
_This field is mandatory and describe which retriever you are using._ | +| `url` | string | **none** | **(mandatory)** Location where to retrieve the file. | +| `method` | string | `GET` | The HTTP Method you are using to call the HTTP endpoint. | +| `body` | string | **none** | The HTTP Body you are using to call the HTTP endpoint. | +| `headers` | map[string][]string | **none** | The HTTP headers used to call when calling the HTTP endpoint (useful for authorization). | +| `timeout` | string | `10000` | Timeout in millisecond used when calling the HTTP endpoint. | + + +### Google Storage + +| Field name | Type | Default | Description | +|------------|--------|----------|-------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`googleStorage`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** This is the name of your Google Storage bucket _(ex: `my-featureflag-bucket`)_. | +| `object` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | + + +### Kubernetes ConfigMap + +_Note that relay proxy is only supporting this while running inside the kubernetes cluster._ + +| Field name | Type | Default | Description | +|-------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`configmap`**.
_This field is mandatory and describe which retriever you are using._ | +| `namespace` | string | **none** | **(mandatory)** This is the name of the namespace where your **configmap** is located _(ex: `default`)_. | +| `configmap` | string | **none** | **(mandatory)** Name of the **configmap** we should read _(ex: `feature-flag`)_. | +| `key` | string | **none** | **(mandatory)** Name of the `key` in the **configmap** which contains the flag. | + + + +## type `exporter` + +### Webhook + +| Field name | Type | Default | Description | +|--------------------|-------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`webhook`**.
_This field is mandatory and describe which retriever you are using._ | +| `endpointUrl` | string | **none** | **(mandatory)** EndpointURL of your webhook. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | +| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | + + +### File + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | +| `outputDir` | string | **none** | **(mandatory)** OutputDir is the location of the directory where to store the exported files. It should finish with a `/`. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` + + +### Log + +| Field name | Type | Default | Description | +|--------------------|--------|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`log`**.
_This field is mandatory and describe which retriever you are using._ | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `logFormat` | string | `[{{ .FormattedDate}}] user="{{ .UserKey}}", flag="{{ .Key}}", value="{{ .Value}}"` | LogFormat is the [template](https://golang.org/pkg/text/template/) configuration of the output format of your log.
You can use all the key from the exporter.FeatureEvent + a key called FormattedDate that represent the date with the RFC 3339 Format. | + +### S3 + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** Name of your S3 Bucket. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` +| `path` | string | **bucket root level** | The location of the directory in S3. | + +### Google Storage + +| Field name | Type | Default | Description | +|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | +| `bucket` | string | **none** | **(mandatory)** Name of your Google Cloud Storage Bucket. | +| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | +| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | +| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | +| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | +| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` +| `path` | string | **bucket root level** | The location of the directory in S3. | + + + + +## type `notifier` + +### Slack + +| Field name | Type | Default | Description | +|-------------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | +| `slackWebhookUrl` | string | **none** | **(mandatory)** The complete URL of your incoming webhook configured in Slack. | + +### Webhook + +| Field name | Type | Default | Description | +|----------------|---------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | +| `endpointUrl` | string | **none** | **(mandatory)** The complete URL of your API (we will send a POST request to this URL, see [format](https://thomaspoignant.github.io/go-feature-flag/latest/notifier/webhook/#format) | +| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | +| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | diff --git a/retriever/retriever.go b/retriever/retriever.go index 5974cfb94f3..0f308d49682 100644 --- a/retriever/retriever.go +++ b/retriever/retriever.go @@ -7,7 +7,5 @@ import ( // Retriever is the interface to create a Retriever to load you flags. type Retriever interface { // Retrieve function is supposed to load the file and to return a []byte of your flag configuration file. - // If you want to specify the format of the file, you can use the ffclient.Config.FileFormat option to - // specify if it is a YAML, JSON or TOML file. Retrieve(ctx context.Context) ([]byte, error) } From 9123a69a32c4825ffd64b9988c7483de4b84f7d8 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 3 Nov 2022 15:48:01 +0100 Subject: [PATCH 5/9] Section for relay proxy Signed-off-by: Thomas Poignant --- .../openfeature_sdk/configure_relay_proxy.md | 177 ------------------ docs/docs/relay_proxy/_category_.json | 4 +- .../getting_started.md} | 2 +- .../install_relay_proxy.md | 0 .../relay_proxy_endpoints.md | 0 5 files changed, 3 insertions(+), 180 deletions(-) delete mode 100644 docs/docs/openfeature_sdk/configure_relay_proxy.md rename docs/docs/{openfeature_sdk/getting_started_relay_proxy.md => relay_proxy/getting_started.md} (98%) rename docs/docs/{openfeature_sdk => relay_proxy}/install_relay_proxy.md (100%) rename docs/docs/{openfeature_sdk => relay_proxy}/relay_proxy_endpoints.md (100%) diff --git a/docs/docs/openfeature_sdk/configure_relay_proxy.md b/docs/docs/openfeature_sdk/configure_relay_proxy.md deleted file mode 100644 index c8befe9a609..00000000000 --- a/docs/docs/openfeature_sdk/configure_relay_proxy.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -sidebar_position: 30 -description: How to configure the relay proxy to serve your feature flags. ---- - -# Configure the relay proxy - -## Global configuration -The configuration of the **relay proxy** is based on a configuration file that you have to provide. - -| Field name | Type | Default | Description | -|---------------------------|-------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `retriever` | [retriever](#retriever) | **none** | **(mandatory)** This is the configuration on how to retrieve the configuration of the files | -| `listen` | int | `1031` | This is the port used by the relay proxy when starting the HTTP server. | -| `pollingInterval` | int | `60000` | This is the time interval **in millisecond** when the relay proxy is reloading the configuration file.
The minimum time accepted is 1000 millisecond. | -| `hideBanner` | boolean | `false` | Should we display the beautiful **go-feature-flag** banner when starting the relay proxy | -| `enableSwagger` | boolean | `false` | Do you want to enable swagger to test the APIs directly. If you are enabling Swagger you will have to provide the `host` configuration and the Swagger UI will be available at `http://:/swagger/`. | -| `host` | string | `localhost` | This is the DNS you will use to access the relay proxy. This field is used by Swagger to query the API at the right place. | -| `restApiTimeout` | int | `5000` | Timeout in millisecond we are accepting to wait in our APIs. | -| `debug` | boolean | `false` | If `true` you will have more logs in the output that will help you to better understand what happen. If an error happen in the API the error will be also shown in the body. | -| `fileFormat` | string | `yaml` | This is the format of your `go-feature-flag` configuration file. Acceptable values are `yaml`, `json`, `toml`. | -| `startWithRetrieverError` | boolean | `false` | By default the **relay proxy** will crash if he is not able to retrieve the flags from the configuration.
If you don't want your relay proxy to crash, you can set `startWithRetrieverError` to true. Until the flag is retrievable the relay proxy will only answer with default values. | -| `exporter` | [exporter](#exporter) | **none** | Exporter is the configuration on how to export data. | -| `notifier` | [notifier](#notifier) | **none** | Notifiers is the configuration on where to notify a flag change. | - - - - -## type `retriver` - -`go-feature-flag` is supporting different kind of retriever such as S3, Google store, etc ... -In this section we will present all the available retriever configuration available. - -### S3 - -| Field name | Type | Default | Description | -|------------|--------|----------|--------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | -| `bucket` | string | **none** | **(mandatory)** This is the name of your S3 bucket _(ex: `my-featureflag-bucket`)_. | -| `item` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | - - -### GitHub - -| Field name | Type | Default | Description | -|------------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`github`**.
_This field is mandatory and describe which retriever you are using._ | -| `repositorySlug` | string | **none** | **(mandatory)** The repository slug of the GitHub repository where your file is located _(ex: `thomaspoignant/go-feature-flag`)_. | -| `path` | string | **none** | **(mandatory)** Path to the file inside the repository _(ex: `config/flag/my-flags.yaml`)_. | -| `branch` | string | `main` | The branch we should check in the repository. | -| `githubToken` | string | **none** | Github token is used to access a private repository, you need the repo permission ([how to create a GitHub token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)). | -| `timeout` | string | `10000` | Timeout in millisecond used when calling GitHub. | - -### File - -| Field name | Type | Default | Description | -|------------|--------|----------|----------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | -| `path` | string | **none** | **(mandatory)** Path to the file in your local computer _(ex: `/goff/my-flags.yaml`)_. | - - -### HTTP - -| Field name | Type | Default | Description | -|------------|---------------------|----------|----------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`http`**.
_This field is mandatory and describe which retriever you are using._ | -| `url` | string | **none** | **(mandatory)** Location where to retrieve the file. | -| `method` | string | `GET` | The HTTP Method you are using to call the HTTP endpoint. | -| `body` | string | **none** | The HTTP Body you are using to call the HTTP endpoint. | -| `headers` | map[string][]string | **none** | The HTTP headers used to call when calling the HTTP endpoint (useful for authorization). | -| `timeout` | string | `10000` | Timeout in millisecond used when calling the HTTP endpoint. | - - -### Google Storage - -| Field name | Type | Default | Description | -|------------|--------|----------|-------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`googleStorage`**.
_This field is mandatory and describe which retriever you are using._ | -| `bucket` | string | **none** | **(mandatory)** This is the name of your Google Storage bucket _(ex: `my-featureflag-bucket`)_. | -| `object` | string | **none** | **(mandatory)** Path to the file inside the bucket _(ex: `config/flag/my-flags.yaml`)_. | - - -### Kubernetes ConfigMap - -_Note that relay proxy is only supporting this while running inside the kubernetes cluster._ - -| Field name | Type | Default | Description | -|-------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`configmap`**.
_This field is mandatory and describe which retriever you are using._ | -| `namespace` | string | **none** | **(mandatory)** This is the name of the namespace where your **configmap** is located _(ex: `default`)_. | -| `configmap` | string | **none** | **(mandatory)** Name of the **configmap** we should read _(ex: `feature-flag`)_. | -| `key` | string | **none** | **(mandatory)** Name of the `key` in the **configmap** which contains the flag. | - - - -## type `exporter` - -### Webhook - -| Field name | Type | Default | Description | -|--------------------|-------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`webhook`**.
_This field is mandatory and describe which retriever you are using._ | -| `endpointUrl` | string | **none** | **(mandatory)** EndpointURL of your webhook. | -| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | -| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | -| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | -| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | - - -### File - -| Field name | Type | Default | Description | -|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`file`**.
_This field is mandatory and describe which retriever you are using._ | -| `outputDir` | string | **none** | **(mandatory)** OutputDir is the location of the directory where to store the exported files. It should finish with a `/`. | -| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | -| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | -| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | -| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | -| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` - - -### Log - -| Field name | Type | Default | Description | -|--------------------|--------|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`log`**.
_This field is mandatory and describe which retriever you are using._ | -| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | -| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | -| `logFormat` | string | `[{{ .FormattedDate}}] user="{{ .UserKey}}", flag="{{ .Key}}", value="{{ .Value}}"` | LogFormat is the [template](https://golang.org/pkg/text/template/) configuration of the output format of your log.
You can use all the key from the exporter.FeatureEvent + a key called FormattedDate that represent the date with the RFC 3339 Format. | - -### S3 - -| Field name | Type | Default | Description | -|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | -| `bucket` | string | **none** | **(mandatory)** Name of your S3 Bucket. | -| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | -| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | -| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | -| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | -| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` -| `path` | string | **bucket root level** | The location of the directory in S3. | - -### Google Storage - -| Field name | Type | Default | Description | -|--------------------|--------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`s3`**.
_This field is mandatory and describe which retriever you are using._ | -| `bucket` | string | **none** | **(mandatory)** Name of your Google Cloud Storage Bucket. | -| `flushInterval` | int | `60000` | The interval in millisecond between 2 calls to the webhook _(if the `maxEventInMemory` is reached before the flushInterval we will call the webhook before)_. | -| `maxEventInMemory` | int | `100000` | If we hit that limit we will call the webhook. | -| `format` | string | `JSON` | Format is the output format you want in your exported file. Available format: `JSON`, `CSV`. | -| `filename` | string | `flag-variation-{{ .Hostname}}-{{ .Timestamp}}.{{ .Format}}` | You can use a templated config to define the name of your exported files. Available replacement are `{{ .Hostname}}`, `{{ .Timestamp}}` and `{{ .Format}` | -| `csvTemplate` | string | `{{ .Kind}};{{ .ContextKind}};{{ .UserKey}};{{ .CreationDate}};{{ .Key}};{{ .Variation}};{{ .Value}};{{ .Default}}\n` | CsvTemplate is used if your output format is CSV.
This field will be ignored if you are using another format than CSV.
You can decide which fields you want in your CSV line with a go-template syntax, please check [`internal/exporter/feature_event.go`](https://github.com/thomaspoignant/go-feature-flag/blob/main/internal/exporter/feature_event.go) to see what are the fields available. |` -| `path` | string | **bucket root level** | The location of the directory in S3. | - - - - -## type `notifier` - -### Slack - -| Field name | Type | Default | Description | -|-------------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | -| `slackWebhookUrl` | string | **none** | **(mandatory)** The complete URL of your incoming webhook configured in Slack. | - -### Webhook - -| Field name | Type | Default | Description | -|----------------|---------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `kind` | string | **none** | **(mandatory)** Value should be **`slack`**.
_This field is mandatory and describe which retriever you are using._ | -| `endpointUrl` | string | **none** | **(mandatory)** The complete URL of your API (we will send a POST request to this URL, see [format](https://thomaspoignant.github.io/go-feature-flag/latest/notifier/webhook/#format) | -| `secret` | string | **none** | Secret used to sign your request body and fill the `X-Hub-Signature-256` header.
See [signature section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/webhook/#signature) for more details. | -| `meta` | map[string]string | **none** | Add all the information you want to see in your request. | diff --git a/docs/docs/relay_proxy/_category_.json b/docs/docs/relay_proxy/_category_.json index 936077f1ef0..f2cc65e1014 100644 --- a/docs/docs/relay_proxy/_category_.json +++ b/docs/docs/relay_proxy/_category_.json @@ -1,10 +1,10 @@ { "position": 50, - "label":"Relay proxy", + "label":"Use the relay proxy", "collapsible": true, "collapsed": true, "link": { "type": "generated-index", - "title": "Relay proxy" + "title": "Use the relay proxy" } } diff --git a/docs/docs/openfeature_sdk/getting_started_relay_proxy.md b/docs/docs/relay_proxy/getting_started.md similarity index 98% rename from docs/docs/openfeature_sdk/getting_started_relay_proxy.md rename to docs/docs/relay_proxy/getting_started.md index abf98a60d58..7b734bfc990 100644 --- a/docs/docs/openfeature_sdk/getting_started_relay_proxy.md +++ b/docs/docs/relay_proxy/getting_started.md @@ -3,7 +3,7 @@ sidebar_position: 21 description: Getting started with the relay proxy. --- -# Getting started with the relay proxy +# Getting started Before starting your **relay proxy** you will need to create a minimal configuration file. diff --git a/docs/docs/openfeature_sdk/install_relay_proxy.md b/docs/docs/relay_proxy/install_relay_proxy.md similarity index 100% rename from docs/docs/openfeature_sdk/install_relay_proxy.md rename to docs/docs/relay_proxy/install_relay_proxy.md diff --git a/docs/docs/openfeature_sdk/relay_proxy_endpoints.md b/docs/docs/relay_proxy/relay_proxy_endpoints.md similarity index 100% rename from docs/docs/openfeature_sdk/relay_proxy_endpoints.md rename to docs/docs/relay_proxy/relay_proxy_endpoints.md From 429a2aec297a45edfbf328a770ec50bb572c75d1 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 4 Nov 2022 12:56:38 +0100 Subject: [PATCH 6/9] Improve swagger doc + display in UI Signed-off-by: Thomas Poignant --- Makefile | 2 +- cmd/relayproxy/controller/all_flags.go | 17 +- cmd/relayproxy/controller/flag_eval.go | 29 ++-- cmd/relayproxy/controller/health.go | 8 +- cmd/relayproxy/controller/info.go | 7 +- cmd/relayproxy/docs/api.md | 6 + cmd/relayproxy/docs/docs.go | 151 +++++++++++------ cmd/relayproxy/docs/swagger.json | 149 +++++++++++------ cmd/relayproxy/docs/swagger.yaml | 153 ++++++++++++------ cmd/relayproxy/modeldocs/eval_docs.go | 28 ++++ docs/src/pages/API_relayproxy/index.js | 25 +++ .../src/pages/API_relayproxy/index.module.css | 13 ++ 12 files changed, 417 insertions(+), 171 deletions(-) create mode 100644 cmd/relayproxy/docs/api.md create mode 100644 cmd/relayproxy/modeldocs/eval_docs.go create mode 100644 docs/src/pages/API_relayproxy/index.js create mode 100644 docs/src/pages/API_relayproxy/index.module.css diff --git a/Makefile b/Makefile index e68c62bae0d..de0e14ea3c7 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ watch-relayproxy: ## Launch the relay proxy in watch mode. swagger: ## Build swagger documentation $(GOCMD) install github.com/swaggo/swag/cmd/swag@latest - cd cmd/relayproxy && swag init --parseDependency --parseDepth 2 + cd cmd/relayproxy && swag init --markdownFiles docs ## Test: test: ## Run the tests of the project diff --git a/cmd/relayproxy/controller/all_flags.go b/cmd/relayproxy/controller/all_flags.go index 790cd05edfa..dffa1adf00b 100644 --- a/cmd/relayproxy/controller/all_flags.go +++ b/cmd/relayproxy/controller/all_flags.go @@ -19,18 +19,21 @@ func NewAllFlags(goFF *ffclient.GoFeatureFlag) Controller { } // Handler is the entry point for the allFlags endpoint -// @Summary allflags returns all the flag for a specific user. -// @Description allflags returns all the flag for a specific user. -// @Tags flags +// @Summary All flags variations for a user +// @Description Making a **POST** request to the URL `/v1/allflags` will give you the values of all the flags for +// @Description this user. +// @Description +// @Description To get a variation you should provide information about the user. +// @Description For that you should provide some user information in JSON in the request body. // @Produce json // @Accept json -// @Param data body model.RelayProxyRequest true "Payload of the user we want to challenge against the flag." +// @Param data body model.AllFlagRequest true "Payload of the user we want to challenge against the flag." // @Success 200 {object} modeldocs.AllFlags "Success" -// @Failure 400 {object} modeldocs.HTTPError "Bad Request" -// @Failure 500 {object} modeldocs.HTTPError "Internal server error" +// @Failure 400 {object} modeldocs.HTTPErrorDoc "Bad Request" +// @Failure 500 {object} modeldocs.HTTPErrorDoc "Internal server error" // @Router /v1/allflags [post] func (h *allFlags) Handler(c echo.Context) error { - reqBody := new(model.RelayProxyRequest) + reqBody := new(model.AllFlagRequest) if err := c.Bind(reqBody); err != nil { return err } diff --git a/cmd/relayproxy/controller/flag_eval.go b/cmd/relayproxy/controller/flag_eval.go index 35fdc174f30..3a613ccfb1d 100644 --- a/cmd/relayproxy/controller/flag_eval.go +++ b/cmd/relayproxy/controller/flag_eval.go @@ -20,28 +20,33 @@ func NewFlagEval(goFF *ffclient.GoFeatureFlag) Controller { } // Handler is the entry point for the flag eval endpoint -// @Summary Evaluate the users with the corresponding flag and return the value for the user. -// @Description Evaluate the users with the corresponding flag and return the value for the user. -// @Description Note that you will always have a usable value in the response, you can use the field failed to know if -// @Description an issue has occurred during the validation of the flag, in that case the value returned will be the -// @Description default value. -// @Tags flags +// @Summary Evaluate a feature flag +// @Description Making a **POST** request to the URL `/v1/feature//eval` will give you the value of the +// @Description flag for this user. +// @Description +// @Description To get a variation you should provide information about the user: +// @Description - User information in JSON in the request body. +// @Description - A default value in case there is an error while evaluating the flag. +// @Description +// @Description Note that you will always have a usable value in the response, you can use the field `failed` to know if +// @Description an issue has occurred during the validation of the flag, in that case the value returned will be the +// @Description default value. // @Produce json // @Accept json -// @Param data body model.RelayProxyRequest true "Payload of the user we want to get all the flags from." +// @Param data body model.EvalFlagRequest true "Payload of the user we want to get all the flags from." // @Param flag_key path string true "Name of your feature flag" -// @Success 200 {object} model.FlagEval "Success" -// @Failure 400 {object} modeldocs.HTTPError "Bad Request" -// @Failure 500 {object} modeldocs.HTTPError "Internal server error" +// @Success 200 {object} modeldocs.EvalFlagDoc "Success" +// @Failure 400 {object} modeldocs.HTTPErrorDoc "Bad Request" +// @Failure 500 {object} modeldocs.HTTPErrorDoc "Internal server error" // @Router /v1/feature/{flag_key}/eval [post] func (h *flagEval) Handler(c echo.Context) error { - reqBody := new(model.RelayProxyRequest) + reqBody := new(model.EvalFlagRequest) if err := c.Bind(reqBody); err != nil { return err } // validation that we have a reqBody key - if err := assertRequest(reqBody); err != nil { + if err := assertRequest(&reqBody.AllFlagRequest); err != nil { return err } goFFUser, err := userRequestToUser(reqBody.User) diff --git a/cmd/relayproxy/controller/health.go b/cmd/relayproxy/controller/health.go index e696dadda8c..76609de255e 100644 --- a/cmd/relayproxy/controller/health.go +++ b/cmd/relayproxy/controller/health.go @@ -19,12 +19,14 @@ func NewHealth(monitoring service.Monitoring) Controller { } // Handler is the entry point for this API -// @Summary Health, status endpoint -// @Description Health is the status endpoint of the relay proxy, you should call it to check if the relay proxy is up. -// @Tags monitoring +// @Summary Health +// @Description Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve traffic. +// @Description +// @Description This is useful especially for loadbalancer to know that they can send traffic to the service. // @Produce json // @Success 200 {object} model.HealthResponse // @Router /health [get] +// func (h *health) Handler(c echo.Context) error { return c.JSON(http.StatusOK, h.monitoringService.Health()) } diff --git a/cmd/relayproxy/controller/info.go b/cmd/relayproxy/controller/info.go index 28fbaa96d30..e8ffc1344b9 100644 --- a/cmd/relayproxy/controller/info.go +++ b/cmd/relayproxy/controller/info.go @@ -18,9 +18,10 @@ func NewInfo(monitoring service.Monitoring) Controller { } // Handler is the entry point for the Info API -// @Summary Info, give information about the instance of go-feature-flag relay proxy -// @Description Info, give information about the instance of go-feature-flag relay proxy -// @Tags monitoring +// @Summary Info +// @Description Making a **GET** request to the URL path `/info` will tell give you information about the actual state of the relay proxy. +// @Description +// @Description As of Today the level of information is small be we can improve this endpoint to returns more information. // @Produce json // @Success 200 {object} model.InfoResponse // @Router /info [get] diff --git a/cmd/relayproxy/docs/api.md b/cmd/relayproxy/docs/api.md new file mode 100644 index 00000000000..bc96f0bc10a --- /dev/null +++ b/cmd/relayproxy/docs/api.md @@ -0,0 +1,6 @@ +# Introduction + +This API is documented in **OpenAPI format** and describe the REST API of the **GO Feature Flag relay proxy**. + +The relay proxy is a component to evaluate your feature flags remotely when using **GO Feature Flag**. +This API is mostly used by all the OpenFeature providers. diff --git a/cmd/relayproxy/docs/docs.go b/cmd/relayproxy/docs/docs.go index 28c57cab807..f81d68b1ee1 100644 --- a/cmd/relayproxy/docs/docs.go +++ b/cmd/relayproxy/docs/docs.go @@ -12,27 +12,29 @@ const docTemplate = `{ "title": "{{.Title}}", "contact": { "name": "GO feature flag relay proxy", - "url": "https://github.com/thomaspoignant/go-feature-flag-relay-proxy" + "url": "https://gofeatureflag.org", + "email": "contact@gofeatureflag.org" }, "license": { "name": "MIT", "url": "https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE" }, - "version": "{{.Version}}" + "version": "{{.Version}}", + "x-logo": { + "altText": "GO Feature Flag logo", + "url": "https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png" + } }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/health": { "get": { - "description": "Health is the status endpoint of the relay proxy, you should call it to check if the relay proxy is up.", + "description": "Making a **GET** request to the URL path ` + "`" + `/health` + "`" + ` will tell you if the relay proxy is ready to serve traffic.\n\nThis is useful especially for loadbalancer to know that they can send traffic to the service.", "produces": [ "application/json" ], - "tags": [ - "monitoring" - ], - "summary": "Health, status endpoint", + "summary": "Health", "responses": { "200": { "description": "OK", @@ -45,14 +47,11 @@ const docTemplate = `{ }, "/info": { "get": { - "description": "Info, give information about the instance of go-feature-flag relay proxy", + "description": "Making a **GET** request to the URL path ` + "`" + `/info` + "`" + ` will tell give you information about the actual state of the relay proxy.\n\nAs of Today the level of information is small be we can improve this endpoint to returns more information.", "produces": [ "application/json" ], - "tags": [ - "monitoring" - ], - "summary": "Info, give information about the instance of go-feature-flag relay proxy", + "summary": "Info", "responses": { "200": { "description": "OK", @@ -65,17 +64,14 @@ const docTemplate = `{ }, "/v1/allflags": { "post": { - "description": "allflags returns all the flag for a specific user.", + "description": "Making a **POST** request to the URL ` + "`" + `/v1/allflags` + "`" + ` will give you the values of all the flags for\nthis user.\n\nTo get a variation you should provide information about the user.\nFor that you should provide some user information in JSON in the request body.", "consumes": [ "application/json" ], "produces": [ "application/json" ], - "tags": [ - "flags" - ], - "summary": "allflags returns all the flag for a specific user.", + "summary": "All flags variations for a user", "parameters": [ { "description": "Payload of the user we want to challenge against the flag.", @@ -83,7 +79,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RelayProxyRequest" + "$ref": "#/definitions/model.AllFlagRequest" } } ], @@ -97,13 +93,13 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } } } @@ -111,17 +107,14 @@ const docTemplate = `{ }, "/v1/feature/{flag_key}/eval": { "post": { - "description": "Evaluate the users with the corresponding flag and return the value for the user.\nNote that you will always have a usable value in the response, you can use the field failed to know if\nan issue has occurred during the validation of the flag, in that case the value returned will be the\ndefault value.", + "description": "Making a **POST** request to the URL ` + "`" + `/v1/feature/\u003cyour_flag_name\u003e/eval` + "`" + ` will give you the value of the\nflag for this user.\n\nTo get a variation you should provide information about the user:\n- User information in JSON in the request body.\n- A default value in case there is an error while evaluating the flag.\n\nNote that you will always have a usable value in the response, you can use the field ` + "`" + `failed` + "`" + ` to know if\nan issue has occurred during the validation of the flag, in that case the value returned will be the\ndefault value.", "consumes": [ "application/json" ], "produces": [ "application/json" ], - "tags": [ - "flags" - ], - "summary": "Evaluate the users with the corresponding flag and return the value for the user.", + "summary": "Evaluate a feature flag", "parameters": [ { "description": "Payload of the user we want to get all the flags from.", @@ -129,7 +122,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RelayProxyRequest" + "$ref": "#/definitions/model.EvalFlagRequest" } }, { @@ -144,19 +137,19 @@ const docTemplate = `{ "200": { "description": "Success", "schema": { - "$ref": "#/definitions/model.FlagEval" + "$ref": "#/definitions/modeldocs.EvalFlagDoc" } }, "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } } } @@ -164,34 +157,44 @@ const docTemplate = `{ } }, "definitions": { - "model.FlagEval": { + "model.AllFlagRequest": { "type": "object", "properties": { - "value": {} + "user": { + "description": "User The representation of a user for your feature flag system.", + "$ref": "#/definitions/model.UserRequest" + } } }, - "model.HealthResponse": { + "model.EvalFlagRequest": { "type": "object", "properties": { - "initialized": { - "type": "boolean" + "defaultValue": { + "description": "The value will we use if we are not able to get the variation of the flag." + }, + "user": { + "description": "User The representation of a user for your feature flag system.", + "$ref": "#/definitions/model.UserRequest" } } }, - "model.InfoResponse": { + "model.HealthResponse": { "type": "object", "properties": { - "cacheRefresh": { - "type": "string" + "initialized": { + "description": "Set to true if the HTTP server is started", + "type": "boolean", + "example": true } } }, - "model.RelayProxyRequest": { + "model.InfoResponse": { "type": "object", "properties": { - "defaultValue": {}, - "user": { - "$ref": "#/definitions/model.UserRequest" + "cacheRefresh": { + "description": "This is the last time when your flag file was read and store in the internal cache.", + "type": "string", + "example": "2022-06-13T11:22:55.941628+02:00" } } }, @@ -200,16 +203,26 @@ const docTemplate = `{ "properties": { "anonymous": { "description": "Anonymous set if this is a logged-in user or not.", - "type": "boolean" + "type": "boolean", + "example": false }, "custom": { "description": "Custom is a map containing all extra information for this user.", "type": "object", - "additionalProperties": true + "additionalProperties": { + "type": "string" + }, + "example": { + "company": "GO Feature Flag", + "email": "contact@gofeatureflag.org", + "firstname": "John", + "lastname": "Doe" + } }, "key": { "description": "Key is the identifier of the UserRequest.", - "type": "string" + "type": "string", + "example": "08b5ffb7-7109-42f4-a6f2-b85560fbd20f" } } }, @@ -225,12 +238,50 @@ const docTemplate = `{ } }, "valid": { - "description": "Valid if false it means there was an error (such as the data store not being available),\nin which case no flag data is in this object.", + "description": "` + "`" + `true` + "`" + ` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue.", "type": "boolean", "example": false } } }, + "modeldocs.EvalFlagDoc": { + "type": "object", + "properties": { + "errorCode": { + "description": "Code of the error returned by the server.", + "type": "string", + "example": "" + }, + "failed": { + "description": "` + "`" + `true` + "`" + ` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue.", + "type": "boolean", + "example": false + }, + "reason": { + "description": "reason why we have returned this value.", + "type": "string", + "example": "TARGETING_MATCH" + }, + "trackEvents": { + "description": "` + "`" + `true` + "`" + ` if the event was tracked by the relay proxy.", + "type": "boolean", + "example": true + }, + "value": { + "description": "The flag value for this user." + }, + "variationType": { + "description": "The variation used to give you this value.", + "type": "string", + "example": "variation-A" + }, + "version": { + "description": "The version of the flag used.", + "type": "string", + "example": "1.0" + } + } + }, "modeldocs.FlagState": { "type": "object", "properties": { @@ -254,11 +305,13 @@ const docTemplate = `{ } } }, - "modeldocs.HTTPError": { + "modeldocs.HTTPErrorDoc": { "type": "object", "properties": { "message": { - "description": "Message of your error" + "description": "Message of your error", + "type": "string", + "example": "An error occurred" } } } @@ -271,8 +324,8 @@ var SwaggerInfo = &swag.Spec{ Host: "", BasePath: "/", Schemes: []string{}, - Title: "go-feature-flag relay proxy", - Description: "Swagger for the go-feature-flag relay proxy.\n\ngo-feature-flag relay proxy is using thomaspoignant/go-feature-flag to handle your feature flagging.\nIt is a proxy to your flags, you can get the values of your flags using APIs.", + Title: "GO Feature Flag relay proxy endpoints", + Description: "# Introduction\n\nThis API is documented in **OpenAPI format** and describe the REST API of the **GO Feature Flag relay proxy**.\n\nThe relay proxy is a component to evaluate your feature flags remotely when using **GO Feature Flag**. \nThis API is mostly used by all the OpenFeature providers.\n", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, } diff --git a/cmd/relayproxy/docs/swagger.json b/cmd/relayproxy/docs/swagger.json index ed726f84108..0db7206b3e8 100644 --- a/cmd/relayproxy/docs/swagger.json +++ b/cmd/relayproxy/docs/swagger.json @@ -1,29 +1,31 @@ { "swagger": "2.0", "info": { - "description": "Swagger for the go-feature-flag relay proxy.\n\ngo-feature-flag relay proxy is using thomaspoignant/go-feature-flag to handle your feature flagging.\nIt is a proxy to your flags, you can get the values of your flags using APIs.", - "title": "go-feature-flag relay proxy", + "description": "# Introduction\n\nThis API is documented in **OpenAPI format** and describe the REST API of the **GO Feature Flag relay proxy**.\n\nThe relay proxy is a component to evaluate your feature flags remotely when using **GO Feature Flag**. \nThis API is mostly used by all the OpenFeature providers.\n", + "title": "GO Feature Flag relay proxy endpoints", "contact": { "name": "GO feature flag relay proxy", - "url": "https://github.com/thomaspoignant/go-feature-flag-relay-proxy" + "url": "https://gofeatureflag.org", + "email": "contact@gofeatureflag.org" }, "license": { "name": "MIT", "url": "https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE" + }, + "x-logo": { + "altText": "GO Feature Flag logo", + "url": "https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png" } }, "basePath": "/", "paths": { "/health": { "get": { - "description": "Health is the status endpoint of the relay proxy, you should call it to check if the relay proxy is up.", + "description": "Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve traffic.\n\nThis is useful especially for loadbalancer to know that they can send traffic to the service.", "produces": [ "application/json" ], - "tags": [ - "monitoring" - ], - "summary": "Health, status endpoint", + "summary": "Health", "responses": { "200": { "description": "OK", @@ -36,14 +38,11 @@ }, "/info": { "get": { - "description": "Info, give information about the instance of go-feature-flag relay proxy", + "description": "Making a **GET** request to the URL path `/info` will tell give you information about the actual state of the relay proxy.\n\nAs of Today the level of information is small be we can improve this endpoint to returns more information.", "produces": [ "application/json" ], - "tags": [ - "monitoring" - ], - "summary": "Info, give information about the instance of go-feature-flag relay proxy", + "summary": "Info", "responses": { "200": { "description": "OK", @@ -56,17 +55,14 @@ }, "/v1/allflags": { "post": { - "description": "allflags returns all the flag for a specific user.", + "description": "Making a **POST** request to the URL `/v1/allflags` will give you the values of all the flags for\nthis user.\n\nTo get a variation you should provide information about the user.\nFor that you should provide some user information in JSON in the request body.", "consumes": [ "application/json" ], "produces": [ "application/json" ], - "tags": [ - "flags" - ], - "summary": "allflags returns all the flag for a specific user.", + "summary": "All flags variations for a user", "parameters": [ { "description": "Payload of the user we want to challenge against the flag.", @@ -74,7 +70,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RelayProxyRequest" + "$ref": "#/definitions/model.AllFlagRequest" } } ], @@ -88,13 +84,13 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } } } @@ -102,17 +98,14 @@ }, "/v1/feature/{flag_key}/eval": { "post": { - "description": "Evaluate the users with the corresponding flag and return the value for the user.\nNote that you will always have a usable value in the response, you can use the field failed to know if\nan issue has occurred during the validation of the flag, in that case the value returned will be the\ndefault value.", + "description": "Making a **POST** request to the URL `/v1/feature/\u003cyour_flag_name\u003e/eval` will give you the value of the\nflag for this user.\n\nTo get a variation you should provide information about the user:\n- User information in JSON in the request body.\n- A default value in case there is an error while evaluating the flag.\n\nNote that you will always have a usable value in the response, you can use the field `failed` to know if\nan issue has occurred during the validation of the flag, in that case the value returned will be the\ndefault value.", "consumes": [ "application/json" ], "produces": [ "application/json" ], - "tags": [ - "flags" - ], - "summary": "Evaluate the users with the corresponding flag and return the value for the user.", + "summary": "Evaluate a feature flag", "parameters": [ { "description": "Payload of the user we want to get all the flags from.", @@ -120,7 +113,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RelayProxyRequest" + "$ref": "#/definitions/model.EvalFlagRequest" } }, { @@ -135,19 +128,19 @@ "200": { "description": "Success", "schema": { - "$ref": "#/definitions/model.FlagEval" + "$ref": "#/definitions/modeldocs.EvalFlagDoc" } }, "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/modeldocs.HTTPError" + "$ref": "#/definitions/modeldocs.HTTPErrorDoc" } } } @@ -155,34 +148,44 @@ } }, "definitions": { - "model.FlagEval": { + "model.AllFlagRequest": { "type": "object", "properties": { - "value": {} + "user": { + "description": "User The representation of a user for your feature flag system.", + "$ref": "#/definitions/model.UserRequest" + } } }, - "model.HealthResponse": { + "model.EvalFlagRequest": { "type": "object", "properties": { - "initialized": { - "type": "boolean" + "defaultValue": { + "description": "The value will we use if we are not able to get the variation of the flag." + }, + "user": { + "description": "User The representation of a user for your feature flag system.", + "$ref": "#/definitions/model.UserRequest" } } }, - "model.InfoResponse": { + "model.HealthResponse": { "type": "object", "properties": { - "cacheRefresh": { - "type": "string" + "initialized": { + "description": "Set to true if the HTTP server is started", + "type": "boolean", + "example": true } } }, - "model.RelayProxyRequest": { + "model.InfoResponse": { "type": "object", "properties": { - "defaultValue": {}, - "user": { - "$ref": "#/definitions/model.UserRequest" + "cacheRefresh": { + "description": "This is the last time when your flag file was read and store in the internal cache.", + "type": "string", + "example": "2022-06-13T11:22:55.941628+02:00" } } }, @@ -191,16 +194,26 @@ "properties": { "anonymous": { "description": "Anonymous set if this is a logged-in user or not.", - "type": "boolean" + "type": "boolean", + "example": false }, "custom": { "description": "Custom is a map containing all extra information for this user.", "type": "object", - "additionalProperties": true + "additionalProperties": { + "type": "string" + }, + "example": { + "company": "GO Feature Flag", + "email": "contact@gofeatureflag.org", + "firstname": "John", + "lastname": "Doe" + } }, "key": { "description": "Key is the identifier of the UserRequest.", - "type": "string" + "type": "string", + "example": "08b5ffb7-7109-42f4-a6f2-b85560fbd20f" } } }, @@ -216,12 +229,50 @@ } }, "valid": { - "description": "Valid if false it means there was an error (such as the data store not being available),\nin which case no flag data is in this object.", + "description": "`true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue.", "type": "boolean", "example": false } } }, + "modeldocs.EvalFlagDoc": { + "type": "object", + "properties": { + "errorCode": { + "description": "Code of the error returned by the server.", + "type": "string", + "example": "" + }, + "failed": { + "description": "`true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue.", + "type": "boolean", + "example": false + }, + "reason": { + "description": "reason why we have returned this value.", + "type": "string", + "example": "TARGETING_MATCH" + }, + "trackEvents": { + "description": "`true` if the event was tracked by the relay proxy.", + "type": "boolean", + "example": true + }, + "value": { + "description": "The flag value for this user." + }, + "variationType": { + "description": "The variation used to give you this value.", + "type": "string", + "example": "variation-A" + }, + "version": { + "description": "The version of the flag used.", + "type": "string", + "example": "1.0" + } + } + }, "modeldocs.FlagState": { "type": "object", "properties": { @@ -245,11 +296,13 @@ } } }, - "modeldocs.HTTPError": { + "modeldocs.HTTPErrorDoc": { "type": "object", "properties": { "message": { - "description": "Message of your error" + "description": "Message of your error", + "type": "string", + "example": "An error occurred" } } } diff --git a/cmd/relayproxy/docs/swagger.yaml b/cmd/relayproxy/docs/swagger.yaml index 50c964cb308..c9164f4c6b2 100644 --- a/cmd/relayproxy/docs/swagger.yaml +++ b/cmd/relayproxy/docs/swagger.yaml @@ -1,36 +1,54 @@ basePath: / definitions: - model.FlagEval: + model.AllFlagRequest: properties: - value: {} + user: + $ref: '#/definitions/model.UserRequest' + description: User The representation of a user for your feature flag system. + type: object + model.EvalFlagRequest: + properties: + defaultValue: + description: The value will we use if we are not able to get the variation + of the flag. + user: + $ref: '#/definitions/model.UserRequest' + description: User The representation of a user for your feature flag system. type: object model.HealthResponse: properties: initialized: + description: Set to true if the HTTP server is started + example: true type: boolean type: object model.InfoResponse: properties: cacheRefresh: + description: This is the last time when your flag file was read and store + in the internal cache. + example: "2022-06-13T11:22:55.941628+02:00" type: string type: object - model.RelayProxyRequest: - properties: - defaultValue: {} - user: - $ref: '#/definitions/model.UserRequest' - type: object model.UserRequest: properties: anonymous: description: Anonymous set if this is a logged-in user or not. + example: false type: boolean custom: - additionalProperties: true + additionalProperties: + type: string description: Custom is a map containing all extra information for this user. + example: + company: GO Feature Flag + email: contact@gofeatureflag.org + firstname: John + lastname: Doe type: object key: description: Key is the identifier of the UserRequest. + example: 08b5ffb7-7109-42f4-a6f2-b85560fbd20f type: string type: object modeldocs.AllFlags: @@ -43,12 +61,41 @@ definitions: description: flags is the list of flag for the user. type: object valid: - description: |- - Valid if false it means there was an error (such as the data store not being available), - in which case no flag data is in this object. + description: '`true` if something went wrong in the relay proxy (flag does + not exists, ...) and we serve the defaultValue.' example: false type: boolean type: object + modeldocs.EvalFlagDoc: + properties: + errorCode: + description: Code of the error returned by the server. + example: "" + type: string + failed: + description: '`true` if something went wrong in the relay proxy (flag does + not exists, ...) and we serve the defaultValue.' + example: false + type: boolean + reason: + description: reason why we have returned this value. + example: TARGETING_MATCH + type: string + trackEvents: + description: '`true` if the event was tracked by the relay proxy.' + example: true + type: boolean + value: + description: The flag value for this user. + variationType: + description: The variation used to give you this value. + example: variation-A + type: string + version: + description: The version of the flag used. + example: "1.0" + type: string + type: object modeldocs.FlagState: properties: timestamp: @@ -67,29 +114,36 @@ definitions: example: variation-A type: string type: object - modeldocs.HTTPError: + modeldocs.HTTPErrorDoc: properties: message: description: Message of your error + example: An error occurred + type: string type: object info: contact: + email: contact@gofeatureflag.org name: GO feature flag relay proxy - url: https://github.com/thomaspoignant/go-feature-flag-relay-proxy - description: |- - Swagger for the go-feature-flag relay proxy. - - go-feature-flag relay proxy is using thomaspoignant/go-feature-flag to handle your feature flagging. - It is a proxy to your flags, you can get the values of your flags using APIs. + url: https://gofeatureflag.org + description: "# Introduction\n\nThis API is documented in **OpenAPI format** and + describe the REST API of the **GO Feature Flag relay proxy**.\n\nThe relay proxy + is a component to evaluate your feature flags remotely when using **GO Feature + Flag**. \nThis API is mostly used by all the OpenFeature providers.\n" license: name: MIT url: https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE - title: go-feature-flag relay proxy + title: GO Feature Flag relay proxy endpoints + x-logo: + altText: GO Feature Flag logo + url: https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png paths: /health: get: - description: Health is the status endpoint of the relay proxy, you should call - it to check if the relay proxy is up. + description: |- + Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve traffic. + + This is useful especially for loadbalancer to know that they can send traffic to the service. produces: - application/json responses: @@ -97,13 +151,13 @@ paths: description: OK schema: $ref: '#/definitions/model.HealthResponse' - summary: Health, status endpoint - tags: - - monitoring + summary: Health /info: get: - description: Info, give information about the instance of go-feature-flag relay - proxy + description: |- + Making a **GET** request to the URL path `/info` will tell give you information about the actual state of the relay proxy. + + As of Today the level of information is small be we can improve this endpoint to returns more information. produces: - application/json responses: @@ -111,22 +165,24 @@ paths: description: OK schema: $ref: '#/definitions/model.InfoResponse' - summary: Info, give information about the instance of go-feature-flag relay - proxy - tags: - - monitoring + summary: Info /v1/allflags: post: consumes: - application/json - description: allflags returns all the flag for a specific user. + description: |- + Making a **POST** request to the URL `/v1/allflags` will give you the values of all the flags for + this user. + + To get a variation you should provide information about the user. + For that you should provide some user information in JSON in the request body. parameters: - description: Payload of the user we want to challenge against the flag. in: body name: data required: true schema: - $ref: '#/definitions/model.RelayProxyRequest' + $ref: '#/definitions/model.AllFlagRequest' produces: - application/json responses: @@ -137,21 +193,25 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/modeldocs.HTTPError' + $ref: '#/definitions/modeldocs.HTTPErrorDoc' "500": description: Internal server error schema: - $ref: '#/definitions/modeldocs.HTTPError' - summary: allflags returns all the flag for a specific user. - tags: - - flags + $ref: '#/definitions/modeldocs.HTTPErrorDoc' + summary: All flags variations for a user /v1/feature/{flag_key}/eval: post: consumes: - application/json description: |- - Evaluate the users with the corresponding flag and return the value for the user. - Note that you will always have a usable value in the response, you can use the field failed to know if + Making a **POST** request to the URL `/v1/feature//eval` will give you the value of the + flag for this user. + + To get a variation you should provide information about the user: + - User information in JSON in the request body. + - A default value in case there is an error while evaluating the flag. + + Note that you will always have a usable value in the response, you can use the field `failed` to know if an issue has occurred during the validation of the flag, in that case the value returned will be the default value. parameters: @@ -160,7 +220,7 @@ paths: name: data required: true schema: - $ref: '#/definitions/model.RelayProxyRequest' + $ref: '#/definitions/model.EvalFlagRequest' - description: Name of your feature flag in: path name: flag_key @@ -172,17 +232,14 @@ paths: "200": description: Success schema: - $ref: '#/definitions/model.FlagEval' + $ref: '#/definitions/modeldocs.EvalFlagDoc' "400": description: Bad Request schema: - $ref: '#/definitions/modeldocs.HTTPError' + $ref: '#/definitions/modeldocs.HTTPErrorDoc' "500": description: Internal server error schema: - $ref: '#/definitions/modeldocs.HTTPError' - summary: Evaluate the users with the corresponding flag and return the value - for the user. - tags: - - flags + $ref: '#/definitions/modeldocs.HTTPErrorDoc' + summary: Evaluate a feature flag swagger: "2.0" diff --git a/cmd/relayproxy/modeldocs/eval_docs.go b/cmd/relayproxy/modeldocs/eval_docs.go new file mode 100644 index 00000000000..575edd61696 --- /dev/null +++ b/cmd/relayproxy/modeldocs/eval_docs.go @@ -0,0 +1,28 @@ +package modeldocs + +// AllFlags model info +// @Description AllFlags contains the full list of all the flags available for the user +type AllFlags struct { + // flags is the list of flag for the user. + Flags map[string]FlagState `json:"flags"` + + // Valid if false it means there was an error (such as the data store not being available), + // in which case no flag data is in this object. + Valid bool `json:"valid" example:"false"` +} + +// FlagState represents the state of an individual feature flag, with regard to a specific user, when it was called. +type FlagState struct { + // Value is the flag value, it can be any JSON types. + Value interface{} `json:"value"` + + // Timestamp is the time when the flag was evaluated. + Timestamp int64 `json:"timestamp" example:"1652113076"` + + // VariationType is the name of the variation used to have the flag value. + VariationType string `json:"variationType" example:"variation-A"` + + // TrackEvents this flag is trackable. + TrackEvents bool `json:"trackEvents" example:"false"` + Failed bool `json:"-"` +} diff --git a/docs/src/pages/API_relayproxy/index.js b/docs/src/pages/API_relayproxy/index.js new file mode 100644 index 00000000000..aaa5b296ccf --- /dev/null +++ b/docs/src/pages/API_relayproxy/index.js @@ -0,0 +1,25 @@ +import { RedocStandalone } from 'redoc'; +import React from 'react'; +import Layout from '@theme/Layout'; +import styles from './index.module.css' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; + +export default function redoc() { + const {siteConfig} = useDocusaurusContext(); + return( + +
+ +
+
+ ); +} diff --git a/docs/src/pages/API_relayproxy/index.module.css b/docs/src/pages/API_relayproxy/index.module.css new file mode 100644 index 00000000000..2856ebeb112 --- /dev/null +++ b/docs/src/pages/API_relayproxy/index.module.css @@ -0,0 +1,13 @@ +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.redocContainer { + /*background-color: var(--goff-sponsor-bg);*/ + background-color: white; +} + +.redocContainer h1{ + /*font-size: 13rem;*/ +} From e116cf617001816309320f20ec6469b66863e176 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 4 Nov 2022 13:43:09 +0100 Subject: [PATCH 7/9] Improve swagger doc + display in UI Signed-off-by: Thomas Poignant --- cmd/relayproxy/controller/utils.go | 4 +- cmd/relayproxy/main.go | 11 +- cmd/relayproxy/model/health_response.go | 3 +- cmd/relayproxy/model/info_response.go | 3 +- cmd/relayproxy/model/user_request.go | 19 +- cmd/relayproxy/modeldocs/HTTPError.go | 4 +- cmd/relayproxy/modeldocs/allflags_docs.go | 3 +- cmd/relayproxy/modeldocs/eval_docs.go | 39 +- docs/docs/configure_flag/flag_format.md | 4 +- docs/docs/configure_flag/rollout/scheduled.md | 6 +- docs/docs/configure_flag/rule_format.md | 6 +- docs/docs/faq.md | 2 +- docs/docs/go_module/configuration.md | 2 +- docs/docs/index.md | 2 +- docs/docs/openfeature_sdk/concepts.md | 2 +- .../docs/relay_proxy/relay_proxy_endpoints.md | 168 +-- docs/docusaurus.config.js | 3 +- docs/package-lock.json | 1339 ++++++++++++++++- docs/package.json | 6 +- docs/src/pages/API_relayproxy/index.js | 2 +- 20 files changed, 1392 insertions(+), 236 deletions(-) diff --git a/cmd/relayproxy/controller/utils.go b/cmd/relayproxy/controller/utils.go index dfe88654d89..f4ec2b7aa16 100644 --- a/cmd/relayproxy/controller/utils.go +++ b/cmd/relayproxy/controller/utils.go @@ -10,7 +10,7 @@ import ( ) // assertRequest is the function which validates the request, if not valid an echo.HTTPError is return. -func assertRequest(u *model.RelayProxyRequest) *echo.HTTPError { +func assertRequest(u *model.AllFlagRequest) *echo.HTTPError { if u == nil || u.User == nil { return echo.NewHTTPError(http.StatusBadRequest, "assertRequest: impossible to find user in request") } @@ -25,7 +25,7 @@ func assertUserKey(userKey string) *echo.HTTPError { return nil } -// userRequestToUser convert a user from the request model.RelayProxyRequest to a go-feature-flag ffuser.User +// userRequestToUser convert a user from the request model.AllFlagRequest to a go-feature-flag ffuser.User func userRequestToUser(u *model.UserRequest) (ffuser.User, error) { if u == nil { return ffuser.User{}, fmt.Errorf("userRequestToUser: impossible to convert user, userRequest nil") diff --git a/cmd/relayproxy/main.go b/cmd/relayproxy/main.go index 9491e1200a0..e2357e20ebf 100644 --- a/cmd/relayproxy/main.go +++ b/cmd/relayproxy/main.go @@ -25,15 +25,14 @@ const banner = `█▀▀ █▀█   █▀▀ █▀▀ ▄▀█  GO Feature Flag Relay Proxy _____________________________________________` -// @title go-feature-flag relay proxy -// @description Swagger for the go-feature-flag relay proxy. -// @description -// @description go-feature-flag relay proxy is using thomaspoignant/go-feature-flag to handle your feature flagging. -// @description It is a proxy to your flags, you can get the values of your flags using APIs. +// @title GO Feature Flag relay proxy endpoints +// @description.markdown // @contact.name GO feature flag relay proxy -// @contact.url https://github.com/thomaspoignant/go-feature-flag-relay-proxy +// @contact.url https://gofeatureflag.org +// @contact.email contact@gofeatureflag.org // @license.name MIT // @license.url https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE +// @x-logo {"url":"https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png", "altText": "GO Feature Flag logo"} // @BasePath / func main() { // Init pFlag for config file diff --git a/cmd/relayproxy/model/health_response.go b/cmd/relayproxy/model/health_response.go index 193e4682c42..c1100441bc1 100644 --- a/cmd/relayproxy/model/health_response.go +++ b/cmd/relayproxy/model/health_response.go @@ -2,5 +2,6 @@ package model // HealthResponse is the object returned by the health API type HealthResponse struct { - Initialized bool `json:"initialized"` + // Set to true if the HTTP server is started + Initialized bool `json:"initialized" example:"true"` } diff --git a/cmd/relayproxy/model/info_response.go b/cmd/relayproxy/model/info_response.go index 28d6f2c3352..cfe1aa18a3e 100644 --- a/cmd/relayproxy/model/info_response.go +++ b/cmd/relayproxy/model/info_response.go @@ -4,5 +4,6 @@ import "time" // InfoResponse is the object returned by the info API type InfoResponse struct { - LatestCacheRefresh time.Time `json:"cacheRefresh"` + // This is the last time when your flag file was read and store in the internal cache. + LatestCacheRefresh time.Time `json:"cacheRefresh" example:"2022-06-13T11:22:55.941628+02:00"` } diff --git a/cmd/relayproxy/model/user_request.go b/cmd/relayproxy/model/user_request.go index 9b222331080..5ffff51feca 100644 --- a/cmd/relayproxy/model/user_request.go +++ b/cmd/relayproxy/model/user_request.go @@ -1,17 +1,24 @@ package model -type RelayProxyRequest struct { - User *UserRequest `json:"user" xml:"user" form:"user" query:"user"` - DefaultValue interface{} `json:"defaultValue" xml:"defaultValue" form:"defaultValue" query:"defaultValue"` +type AllFlagRequest struct { + // User The representation of a user for your feature flag system. + User *UserRequest `json:"user" xml:"user" form:"user" query:"user"` } +type EvalFlagRequest struct { + AllFlagRequest `json:",inline" yaml:",inline" toml:",inline"` + // The value will we use if we are not able to get the variation of the flag. + DefaultValue interface{} `json:"defaultValue" xml:"defaultValue" form:"defaultValue" query:"defaultValue"` +} + +// UserRequest The representation of a user for your feature flag system. type UserRequest struct { // Key is the identifier of the UserRequest. - Key string `json:"key" xml:"key" form:"key" query:"key"` + Key string `json:"key" xml:"key" form:"key" query:"key" example:"08b5ffb7-7109-42f4-a6f2-b85560fbd20f"` // Anonymous set if this is a logged-in user or not. - Anonymous bool `json:"anonymous" xml:"anonymous" form:"anonymous" query:"anonymous"` + Anonymous bool `json:"anonymous" xml:"anonymous" form:"anonymous" query:"anonymous" example:"false"` // Custom is a map containing all extra information for this user. - Custom map[string]interface{} `json:"custom" xml:"custom" form:"custom" query:"custom"` + Custom map[string]interface{} `json:"custom" xml:"custom" form:"custom" query:"custom" swaggertype:"object,string" example:"email:contact@gofeatureflag.org,firstname:John,lastname:Doe,company:GO Feature Flag"` } diff --git a/cmd/relayproxy/modeldocs/HTTPError.go b/cmd/relayproxy/modeldocs/HTTPError.go index ee528b5e8fc..734a008b3ca 100644 --- a/cmd/relayproxy/modeldocs/HTTPError.go +++ b/cmd/relayproxy/modeldocs/HTTPError.go @@ -1,6 +1,6 @@ package modeldocs -type HTTPError struct { +type HTTPErrorDoc struct { // Message of your error - Message interface{} `json:"message"` + Message string `json:"message" example:"An error occurred"` } diff --git a/cmd/relayproxy/modeldocs/allflags_docs.go b/cmd/relayproxy/modeldocs/allflags_docs.go index 575edd61696..b67eb74dfee 100644 --- a/cmd/relayproxy/modeldocs/allflags_docs.go +++ b/cmd/relayproxy/modeldocs/allflags_docs.go @@ -6,8 +6,7 @@ type AllFlags struct { // flags is the list of flag for the user. Flags map[string]FlagState `json:"flags"` - // Valid if false it means there was an error (such as the data store not being available), - // in which case no flag data is in this object. + // `true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue. Valid bool `json:"valid" example:"false"` } diff --git a/cmd/relayproxy/modeldocs/eval_docs.go b/cmd/relayproxy/modeldocs/eval_docs.go index 575edd61696..9ca458f48c6 100644 --- a/cmd/relayproxy/modeldocs/eval_docs.go +++ b/cmd/relayproxy/modeldocs/eval_docs.go @@ -1,28 +1,19 @@ package modeldocs -// AllFlags model info -// @Description AllFlags contains the full list of all the flags available for the user -type AllFlags struct { - // flags is the list of flag for the user. - Flags map[string]FlagState `json:"flags"` - - // Valid if false it means there was an error (such as the data store not being available), - // in which case no flag data is in this object. - Valid bool `json:"valid" example:"false"` -} - -// FlagState represents the state of an individual feature flag, with regard to a specific user, when it was called. -type FlagState struct { - // Value is the flag value, it can be any JSON types. - Value interface{} `json:"value"` - - // Timestamp is the time when the flag was evaluated. - Timestamp int64 `json:"timestamp" example:"1652113076"` - - // VariationType is the name of the variation used to have the flag value. +// EvalFlagDoc is the documentation struct for the Swagger doc. +type EvalFlagDoc struct { + // `true` if the event was tracked by the relay proxy. + TrackEvents bool `json:"trackEvents" example:"true"` + // The variation used to give you this value. VariationType string `json:"variationType" example:"variation-A"` - - // TrackEvents this flag is trackable. - TrackEvents bool `json:"trackEvents" example:"false"` - Failed bool `json:"-"` + //`true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue. + Failed bool `json:"failed" example:"false"` + // The version of the flag used. + Version string `json:"version" example:"1.0"` + // reason why we have returned this value. + Reason string `json:"reason" example:"TARGETING_MATCH"` + // Code of the error returned by the server. + ErrorCode string `json:"errorCode" example:""` + // The flag value for this user. + Value interface{} `json:"value"` } diff --git a/docs/docs/configure_flag/flag_format.md b/docs/docs/configure_flag/flag_format.md index 52b00a4c782..ef3a66ba07c 100644 --- a/docs/docs/configure_flag/flag_format.md +++ b/docs/docs/configure_flag/flag_format.md @@ -241,14 +241,14 @@ version = "12"

Scheduled allow to patch your flag over time.

You can add several steps that updates the flag, this is typically used if you want to gradually add more user in your flag.

-

See Scheduled rollout to have more info on how to use it.

+

See Scheduled rollout to have more info on how to use it.

experimentation
(optional)

Experimentation allow you to configure a start date and an end date for your flag. When the experimentation is not running, the flag will serve the default value.

-

See Experimentation rollout to have more info on how to use it.

+

See Experimentation rollout to have more info on how to use it.

diff --git a/docs/docs/configure_flag/rollout/scheduled.md b/docs/docs/configure_flag/rollout/scheduled.md index 2fc3481f002..2c2f74d93b9 100644 --- a/docs/docs/configure_flag/rollout/scheduled.md +++ b/docs/docs/configure_flag/rollout/scheduled.md @@ -134,6 +134,6 @@ You can change any fields that are available on your flag. Since your configuration has not been changed manually, it does not trigger any notifier. ::: -| Field | Description | -|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **`steps`** | The only mandatory field in a **step** is the `date`.
**If no date is provided the step will be skipped.**

The other attributes of your `step` are what you want to update your flag, so every field available in the [flag format](../../flag_format) can be updated.
The new value in a field will override the existing one. | +| Field | Description | +|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`steps`** | The only mandatory field in a **step** is the `date`.
**If no date is provided the step will be skipped.**

The other attributes of your `step` are what you want to update your flag, so every field available in the [flag format](../flag_format) can be updated.
The new value in a field will override the existing one. | diff --git a/docs/docs/configure_flag/rule_format.md b/docs/docs/configure_flag/rule_format.md index 73236e491a0..8d0647c8643 100644 --- a/docs/docs/configure_flag/rule_format.md +++ b/docs/docs/configure_flag/rule_format.md @@ -21,7 +21,7 @@ A rule is a configuration that allows to serve a variation based on some conditi name
(optional) - Name of your rule.
This is needed when your are updating a rule using a scheduled rollout. + Name of your rule.
This is needed when your are updating a rule using a scheduled rollout. query @@ -56,7 +56,7 @@ A rule is a configuration that allows to serve a variation based on some conditi

You can decide at which percentage you starts with and at what percentage you ends with in your release ramp. Before the start date we will serve the initial percentage and, after we will serve the end percentage.

-

See progressive rollout to have more info on how to use it.

+

See progressive rollout to have more info on how to use it.

@@ -116,7 +116,7 @@ Compare Expression and their definitions (`a|b` means you can use either one of ## Environments -When you initialise `go-feature-flag` you can set an [environment](../configuration/#option_environment) for the instance of this SDK. +When you initialise `go-feature-flag` you can set an [environment](../go_module/configuration/#option_environment) for the instance of this SDK. ```go linenums="1" ffclient.Init(ffclient.Config{ diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 01c481258d7..186c83e42c4 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -17,7 +17,7 @@ lifecycle of features. The lifecycle of your flags is key if you don't want to have un-used things everywhere in your code. 1. Start by creating the flag in your configuration file *(with 0% to avoid affecting your users)*. -2. Evaluate the flag in your code *(see [variation](./configure_flag/target_user.md#variation))*. +2. Evaluate the flag in your code *(see [variation](./go_module/target_user.md#variation))*. 3. Deploy your application with the variation check. 4. Start rolling out your flag. 5. When 100% of your users have access to the new feature, remove the call to the variation from your code base. diff --git a/docs/docs/go_module/configuration.md b/docs/docs/go_module/configuration.md index fdabd258c50..864976a9f86 100644 --- a/docs/docs/go_module/configuration.md +++ b/docs/docs/go_module/configuration.md @@ -13,7 +13,7 @@ During the initialization you must give a [`ffclient.Config{}`](https://pkg.go.d | Field | Description | |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `Retriever` | The configuration retriever you want to use to get your flag file
*See [Store your flag file](../store_file/index.md) for the configuration details*. | +| `Retriever` | The configuration retriever you want to use to get your flag file
*See [Store your flag file](./store_file/index.md) for the configuration details*. | | `Context` | *(optional)*
The context used by the retriever.
Default: `context.Background()` | | `Environment` | *(optional)*
The environment the app is running under, can be checked in feature flag rules.
Default: `""`
*Check [**"environments"** section](../configure_flag/flag_format/#environments) to understand how to use this parameter.* | | `DataExporter` | *(optional)*
DataExporter defines how to export data on how your flags are used.
*see [export data section](data_collection/index.md) for more details*. | diff --git a/docs/docs/index.md b/docs/docs/index.md index 9931d77b949..bb4da44800c 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -25,7 +25,7 @@ I've also written an [article](https://medium.com/better-programming/feature-fla ## What can I do with GO Feature Flag? -- Storing your configuration flags file on various locations ([`HTTP`](./store_file/http.md), [`S3`](./store_file/s3.md), [`GitHub`](./store_file/github.md), [`file`](./store_file/file.md)). +- Storing your configuration flags file on various locations (`HTTP`, `S3`, `Kubernetes`, [_see full list_](configure_flag/store_your_flags.md). - Configuring your flags in various [format](configure_flag/flag_format.md) (`JSON`, `TOML` and `YAML`). - Adding complex [rules](configure_flag/flag_format.md#rule-format) to target your users. - Use complex rollout strategy for your flags : diff --git a/docs/docs/openfeature_sdk/concepts.md b/docs/docs/openfeature_sdk/concepts.md index 0efb3fc7b26..e45f35dafff 100644 --- a/docs/docs/openfeature_sdk/concepts.md +++ b/docs/docs/openfeature_sdk/concepts.md @@ -23,7 +23,7 @@ To be compatible with our solution, we offer [`providers`](https://docs.openfeat To use the OpenFeature SDKs you need what we call a provider. A **provider** is responsible for performing flag evaluations. It provides an abstraction between **GO Feature Flag** and the OpenFeature SDK. -A provider need a backend service to perform the flag evaluation. This is why we have introduced the **relay proxy**. +A provider need a backend service to perform the flag evaluation. This is why we have introduced the [**relay proxy**](../category/use-the-relay-proxy). This component retrieve your feature flag configuration file using the GO module and expose APIs to get your flags variations. ![](/docs/openfeature/concepts.jpg) diff --git a/docs/docs/relay_proxy/relay_proxy_endpoints.md b/docs/docs/relay_proxy/relay_proxy_endpoints.md index 2f722758936..1cb981b111b 100644 --- a/docs/docs/relay_proxy/relay_proxy_endpoints.md +++ b/docs/docs/relay_proxy/relay_proxy_endpoints.md @@ -7,181 +7,19 @@ description: Description of the available endpoints in the relay proxy. The most updated documentation about the relay proxy endpoints is the Swagger docs _(see [Swagger section](#swagger) to see how to access to the documentation)_. -## Specific to Relay Proxy - -### Health (health check) -Making a `GET` request to the URL path `/health` will tell you if the **relay proxy** is ready to serve traffic. -This is useful especially for loadbalancer to know that they can send traffic to the service. - -```json -{ "initialized": true } -``` - -### Info -Making a `GET` request to the URL path `/info` will tell give you information about the actual state of the **relay proxy**. -As of Today the level of information is small be we can improve this endpoint to returns more information. - -```json -{ - "cacheRefresh": "2022-06-13T11:22:55.941628+02:00" -} -``` - -| Field name | Type | Description | -|--------------------|------|-------------------------------------------------------------------------------------| -| `cacheRefresh` | date | This is the last time when your flag file was read and store in the internal cache. | - - ### Swagger Swagger endpoint is serving a [swagger UI](https://swagger.io/tools/swagger-ui/) to test your REST endpoints. By default, this endpoint is not exposed, you need to have this configuration in your **relay proxy** configuration file: ```yaml # ... + enableSwagger: true host: my-proxy-domain.com # the DNS to access the proxy ``` When enabled, you can go to the `/swagger/` endpoint with your browser, and you will have access to the Swagger UI for the relay proxy. -## Proxies for GO Feature Flag services - -### Endpoint to get variation for a flag - -Making a `POST` request to the URL `/v1/feature//eval` will give you the value of the flag for this user. -To get a variation you should provide information about the user. -For that you should provide some user information in `JSON` in the request body. - -#### Request -**Example:** -```json -{ - "user": { - "key": "123e4567-e89b-12d3-a456-426614174000", - "anonymous": false, - "custom": { - "firstname": "John", - "lastname": "Doe", - "email": "john.doe@random.io" - } - }, - "defaultValue": "default_value_provided_by_SDK" -} -``` - -| Field name | Type | Description | -|--------------------|--------------------|--------------------------------------------------------------------------------------------| -| `user` | [user](#user_type) | **(mandatory)** The representation of a user for your feature flag system. | -| `defaultValue` | any | **(mandatory)** The value will we use if we are not able to get the variation of the flag. | - - -#### Response - -Based on the name of the flag and the user you will always have a response for the variation. -The API will respond with a `200` even if the flag is not available, the goal is for your application to not crash even if -the flag does not exist anymore. - -**Example:** -```json -{ - "value": "welcome_new_feature", - "variationType": "true", - "version": "0", - "trackEvents": true, - "failed": false -} -``` - - - -| Field name | Type | Description | -|-----------------|---------|--------------------------------------------------------------------------------------------------------------------| -| `value` | any | The flag value for this user. | -| `variationType` | string | The variation used to give you this value. | -| `version` | string | The version of the flag used. | -| `trackEvents` | boolean | `true` if the event was tracked by the relay proxy. | -| `failed` | boolean | `true` if something went wrong in the relay proxy _(flag does not exists, ...)_ and we serve the **defaultValue**. | - - - -### Endpoint to get all flags variations for a user - -Making a `POST` request to the URL `/v1/allflags` will give you the values of all the flags for this user. -To get a variation you should provide information about the user. -For that you should provide some user information in `JSON` in the request body. - -#### Request -**Example:** -```json -{ - "user": { - "key": "123e4567-e89b-12d3-a456-426614174000", - "anonymous": false, - "custom": { - "firstname": "John", - "lastname": "Doe", - "email": "john.doe@random.io" - } - } -} -``` - -| Field name | Type | Description | -|--------------------|--------------------|--------------------------------------------------------------------------------------------| -| `user` | [user](#user_type) | **(mandatory)** The representation of a user for your feature flag system. | - -#### Response - -With the input user the API will loop over all flags and get values for all of them. - -**Example:** -```json -{ - "flags": { - "flag-only-for-admin": { - "value": false, - "timestamp": 1655123971, - "variationType": "Default", - "trackEvents": true - }, - "new-admin-access": { - "value": false, - "timestamp": 1655123971, - "variationType": "False", - "trackEvents": true - } - }, - "valid": true -} -``` - -| Field name | Type | Description | -|---------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| `flags` | map[string]variationResult | All the flags with their results _(see [Endpoint to get all flags variations for a user](#variation_results_details) for the details of the format)_ | -| `valid` | boolean | `true` if something went wrong in the relay proxy _(flag does not exists, ...)_ and we serve the **defaultValue**. | - - - -## User type - -This type represent in your request body the information about a user. -This is based on these information that we will be able to filter which variation apply for this user. - -```json -{ - "key": "123e4567-e89b-12d3-a456-426614174000", - "anonymous": false, - "custom": { - "firstname": "John", - "lastname": "Doe", - "email": "john.doe@random.io" - } -} -``` - -| Field name | Type | Default | Description | -|-------------|------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------| -| `key` | string | **none** | **(mandatory)** Unique key of your user, it could be any string, I recommend to use UUID, email or whatever who make your user unique. | -| `anonymous` | boolean | `false` | Is it an authenticated user or not. | -| `custom` | map[string]interface{} | **empty** | This is an object where you can put everything you think is useful, you will be able to use rule based on these fields. | +## [OpenAPI documentation](/API_relayproxy) +If you don't want to install the relay proxy to check the endpoints, you can go to this [**OpenAPI documentation**](/API_relayproxy) directly. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 2ea1fc6954a..54fb345865b 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -24,7 +24,8 @@ const config = { github: "https://github.com/thomaspoignant/go-feature-flag", sponsor: "https://github.com/sponsors/thomaspoignant", openfeature: "https://openfeature.dev", - mailchimpURL: "//gofeatureflag.us14.list-manage.com/subscribe/post?u=86acc1a78e371bf66a9683672&id=f42abfec51&" + mailchimpURL: "//gofeatureflag.us14.list-manage.com/subscribe/post?u=86acc1a78e371bf66a9683672&id=f42abfec51&", + swaggerURL: "https://github.com/thomaspoignant/go-feature-flag/blob/main/cmd/relayproxy/docs/swagger.yaml" }, // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want diff --git a/docs/package-lock.json b/docs/package-lock.json index 1d3e977191b..20ce7652813 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -12,10 +12,14 @@ "@docusaurus/preset-classic": "^2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", + "core-js": "^3.26.0", + "mobx": "^6.6.2", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-mailchimp-subscribe": "^2.1.3" + "react-mailchimp-subscribe": "^2.1.3", + "redoc": "^2.0.0", + "styled-components": "^5.3.6" }, "devDependencies": { "@docusaurus/module-type-aliases": "^2.2.0" @@ -2553,6 +2557,29 @@ "node": ">=16.14" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -2606,6 +2633,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.9.tgz", + "integrity": "sha512-dGGHpb61hLwifAu7sotuHFDBw6GTdpG8aKC0fsK17EuTzMRvUrH7lEAr6LTJ+sx3AZYed9yZ77rltVDHyg2hRg==" + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -2913,6 +2945,75 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==" }, + "node_modules/@redocly/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.0.0-beta.112", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.112.tgz", + "integrity": "sha512-VNJPQwLUvTtgsMrd4CAPnl6zJWvggHE50LCOhnQAReyo4Em7HAGYYo9v920HbIgZ5LCB6Y/f5QUTINrgYPaTkg==", + "dependencies": { + "@redocly/ajv": "^8.11.0", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^5.0.1", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/@types/node": { + "version": "14.18.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", + "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==" + }, + "node_modules/@redocly/openapi-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -4074,6 +4175,11 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -4314,6 +4420,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4350,6 +4461,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -4502,6 +4621,11 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -4564,6 +4688,53 @@ "node": ">=8" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -4907,9 +5078,9 @@ } }, "node_modules/core-js": { - "version": "3.25.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.5.tgz", - "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -4987,6 +5158,14 @@ "node": ">=8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -5130,6 +5309,16 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -5288,6 +5477,11 @@ } } }, + "node_modules/decko": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", + "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==" + }, "node_modules/decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -5544,6 +5738,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + }, "node_modules/domutils": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", @@ -5684,6 +5883,11 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6251,6 +6455,11 @@ "optional": true, "peer": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -6494,6 +6703,11 @@ } } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", @@ -6663,6 +6877,14 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -7295,6 +7517,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7822,6 +8049,14 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -7866,6 +8101,14 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dependencies": { + "foreach": "^2.0.4" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -8051,6 +8294,11 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8106,6 +8354,11 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -8128,6 +8381,11 @@ "semver": "bin/semver.js" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" + }, "node_modules/markdown-escapes": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", @@ -8137,6 +8395,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.1.tgz", + "integrity": "sha512-VK1/jNtwqDLvPktNpL0Fdg3qoeUZhmRsuiIjPEy/lHwXW4ouLoZfO4XoWd4ClDt+hupV1VLpkZhEovjU0W/kqA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/mdast-squeeze-paragraphs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", @@ -8393,6 +8662,60 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mobx": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.6.2.tgz", + "integrity": "sha512-IOpS0bf3+hXIhDIy+CmlNMBfFpAbHS0aVHcNC+xH/TFYEKIIVDKNYRh9eKlXuVfJ1iRKAp0cRVmO145CyJAMVQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.5.3.tgz", + "integrity": "sha512-+ltotliKt4Bjn3d8taZH/VFAcRUbaASvsM8/QSvmHXcZ++RZwaFtjl9JkIosy1byaJGEDS3EFFx2InRm2VaSUw==", + "dependencies": { + "mobx-react-lite": "^3.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-react/node_modules/mobx-react-lite": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", @@ -8485,6 +8808,17 @@ } } }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -8493,6 +8827,14 @@ "node": ">= 6.13.0" } }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "dependencies": { + "es6-promise": "^3.2.1" + } + }, "node_modules/node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -8552,6 +8894,71 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8655,6 +9062,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-sampler": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", + "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", + "dependencies": { + "@types/json-schema": "^7.0.7", + "json-pointer": "0.6.2" + } + }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -8869,6 +9285,11 @@ "tslib": "^2.0.3" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8919,6 +9340,11 @@ "node": ">=8" } }, + "node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -8999,6 +9425,25 @@ "node": ">=4" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/polished": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", + "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss": { "version": "8.4.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", @@ -10085,6 +10530,18 @@ "react": ">=15" } }, + "node_modules/react-tabs": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", + "integrity": "sha512-jx325RhRVnS9DdFbeF511z0T0WEqEoMl1uCE3LoZ6VaZZm7ytatxbum0B8bCTmaiV0KsU+4TtLGTGevCic7SWg==", + "dependencies": { + "clsx": "^1.1.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0-0" + } + }, "node_modules/react-textarea-autosize": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz", @@ -10189,6 +10646,54 @@ "node": ">=6.0.0" } }, + "node_modules/redoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0.tgz", + "integrity": "sha512-rU8iLdAkT89ywOkYk66Mr+IofqaMASlRvTew0dJvopCORMIPUcPMxjlJbJNC6wsn2vvMnpUFLQ/0ISDWn9BWag==", + "dependencies": { + "@redocly/openapi-core": "^1.0.0-beta.104", + "classnames": "^2.3.1", + "decko": "^1.2.0", + "dompurify": "^2.2.8", + "eventemitter3": "^4.0.7", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.0.15", + "mobx-react": "^7.2.0", + "openapi-sampler": "^1.3.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.1.3", + "prismjs": "^1.27.0", + "prop-types": "^15.7.2", + "react-tabs": "^3.2.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "style-loader": "^3.3.1", + "swagger2openapi": "^7.0.6", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=6.9", + "npm": ">=3.0.0" + }, + "peerDependencies": { + "core-js": "^3.1.4", + "mobx": "^6.0.4", + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0", + "styled-components": "^4.1.1 || ^5.1.1" + } + }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -10571,6 +11076,14 @@ "node": ">=0.10" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -11123,6 +11636,54 @@ "node": ">=4" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -11190,6 +11751,14 @@ "node": ">=8" } }, + "node_modules/slugify": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", + "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -11303,6 +11872,11 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.2.1.tgz", "integrity": "sha512-D/uYFWkI/31OrnKmXZqGAGK5GbQRPp/BWA1nuITcc6ICblhhuQUPHS5E2GSCVS7Hwhf4ciq8qsATwBUxv+lI6w==" }, + "node_modules/stickyfill": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", + "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -11403,6 +11977,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -11411,6 +12000,70 @@ "inline-style-parser": "0.1.1" } }, + "node_modules/styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/styled-components/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/styled-components/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -11544,6 +12197,32 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -12267,6 +12946,11 @@ "node": ">=4" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12961,17 +13645,73 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==" + }, + "node_modules/yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">= 6" + "node": ">=8" } }, "node_modules/yocto-queue": { @@ -14788,6 +15528,29 @@ "tslib": "^2.4.0" } }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -14825,6 +15588,11 @@ } } }, + "@exodus/schemasafe": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.9.tgz", + "integrity": "sha512-dGGHpb61hLwifAu7sotuHFDBw6GTdpG8aKC0fsK17EuTzMRvUrH7lEAr6LTJ+sx3AZYed9yZ77rltVDHyg2hRg==" + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -15065,6 +15833,69 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==" }, + "@redocly/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "dependencies": { + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, + "@redocly/openapi-core": { + "version": "1.0.0-beta.112", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.112.tgz", + "integrity": "sha512-VNJPQwLUvTtgsMrd4CAPnl6zJWvggHE50LCOhnQAReyo4Em7HAGYYo9v920HbIgZ5LCB6Y/f5QUTINrgYPaTkg==", + "requires": { + "@redocly/ajv": "^8.11.0", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^5.0.1", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "dependencies": { + "@types/node": { + "version": "14.18.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", + "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -15978,6 +16809,11 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -16159,6 +16995,11 @@ "get-intrinsic": "^1.0.2" } }, + "call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -16183,6 +17024,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -16280,6 +17126,11 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -16324,6 +17175,43 @@ } } }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -16578,9 +17466,9 @@ } }, "core-js": { - "version": "3.25.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.5.tgz", - "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==" + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" }, "core-js-compat": { "version": "3.25.5", @@ -16635,6 +17523,11 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -16718,6 +17611,16 @@ "nth-check": "^2.0.1" } }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -16823,6 +17726,11 @@ "ms": "2.1.2" } }, + "decko": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", + "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==" + }, "decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -17008,6 +17916,11 @@ "domelementtype": "^2.3.0" } }, + "dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + }, "domutils": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", @@ -17122,6 +18035,11 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -17544,6 +18462,11 @@ "optional": true, "peer": true }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -17727,6 +18650,11 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, + "foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, "fork-ts-checker-webpack-plugin": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", @@ -17838,6 +18766,11 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -18323,6 +19256,11 @@ } } }, + "http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -18675,6 +19613,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -18710,6 +19653,14 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "requires": { + "foreach": "^2.0.4" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -18859,6 +19810,11 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -18905,6 +19861,11 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -18920,11 +19881,21 @@ } } }, + "mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" + }, "markdown-escapes": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" }, + "marked": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.1.tgz", + "integrity": "sha512-VK1/jNtwqDLvPktNpL0Fdg3qoeUZhmRsuiIjPEy/lHwXW4ouLoZfO4XoWd4ClDt+hupV1VLpkZhEovjU0W/kqA==" + }, "mdast-squeeze-paragraphs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", @@ -19104,6 +20075,27 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, + "mobx": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.6.2.tgz", + "integrity": "sha512-IOpS0bf3+hXIhDIy+CmlNMBfFpAbHS0aVHcNC+xH/TFYEKIIVDKNYRh9eKlXuVfJ1iRKAp0cRVmO145CyJAMVQ==" + }, + "mobx-react": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.5.3.tgz", + "integrity": "sha512-+ltotliKt4Bjn3d8taZH/VFAcRUbaASvsM8/QSvmHXcZ++RZwaFtjl9JkIosy1byaJGEDS3EFFx2InRm2VaSUw==", + "requires": { + "mobx-react-lite": "^3.4.0" + }, + "dependencies": { + "mobx-react-lite": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "requires": {} + } + } + }, "mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", @@ -19170,11 +20162,27 @@ "whatwg-url": "^5.0.0" } }, + "node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "requires": { + "http2-client": "^1.2.5" + } + }, "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, + "node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "requires": { + "es6-promise": "^3.2.1" + } + }, "node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -19216,6 +20224,56 @@ "boolbase": "^1.0.0" } }, + "oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "requires": { + "fast-safe-stringify": "^2.0.7" + } + }, + "oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "requires": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + } + }, + "oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "requires": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + } + }, + "oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==" + }, + "oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "requires": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -19286,6 +20344,15 @@ "is-wsl": "^2.2.0" } }, + "openapi-sampler": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", + "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", + "requires": { + "@types/json-schema": "^7.0.7", + "json-pointer": "0.6.2" + } + }, "opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -19444,6 +20511,11 @@ "tslib": "^2.0.3" } }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19482,6 +20554,11 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -19540,6 +20617,19 @@ } } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" + }, + "polished": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", + "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "requires": { + "@babel/runtime": "^7.17.8" + } + }, "postcss": { "version": "8.4.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", @@ -20268,6 +21358,15 @@ "tiny-warning": "^1.0.0" } }, + "react-tabs": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", + "integrity": "sha512-jx325RhRVnS9DdFbeF511z0T0WEqEoMl1uCE3LoZ6VaZZm7ytatxbum0B8bCTmaiV0KsU+4TtLGTGevCic7SWg==", + "requires": { + "clsx": "^1.1.0", + "prop-types": "^15.5.0" + } + }, "react-textarea-autosize": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz", @@ -20341,6 +21440,40 @@ "minimatch": "^3.0.5" } }, + "redoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0.tgz", + "integrity": "sha512-rU8iLdAkT89ywOkYk66Mr+IofqaMASlRvTew0dJvopCORMIPUcPMxjlJbJNC6wsn2vvMnpUFLQ/0ISDWn9BWag==", + "requires": { + "@redocly/openapi-core": "^1.0.0-beta.104", + "classnames": "^2.3.1", + "decko": "^1.2.0", + "dompurify": "^2.2.8", + "eventemitter3": "^4.0.7", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.0.15", + "mobx-react": "^7.2.0", + "openapi-sampler": "^1.3.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.1.3", + "prismjs": "^1.27.0", + "prop-types": "^15.7.2", + "react-tabs": "^3.2.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "style-loader": "^3.3.1", + "swagger2openapi": "^7.0.6", + "url-template": "^2.0.8" + } + }, + "reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -20635,6 +21768,11 @@ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -21049,6 +22187,54 @@ "rechoir": "^0.6.2" } }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==" + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -21102,6 +22288,11 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "slugify": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", + "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==" + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -21191,6 +22382,11 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.2.1.tgz", "integrity": "sha512-D/uYFWkI/31OrnKmXZqGAGK5GbQRPp/BWA1nuITcc6ICblhhuQUPHS5E2GSCVS7Hwhf4ciq8qsATwBUxv+lI6w==" }, + "stickyfill": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", + "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -21257,6 +22453,12 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "requires": {} + }, "style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -21265,6 +22467,50 @@ "inline-style-parser": "0.1.1" } }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "dependencies": { + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -21358,6 +22604,24 @@ } } }, + "swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "requires": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + } + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -21834,6 +23098,11 @@ "prepend-http": "^2.0.0" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -22305,6 +23574,11 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -22315,6 +23589,47 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, + "yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==" + }, + "yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/docs/package.json b/docs/package.json index 9ffe0267878..d8fb6243f17 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,10 +18,14 @@ "@docusaurus/preset-classic": "^2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", + "core-js": "^3.26.0", + "mobx": "^6.6.2", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-mailchimp-subscribe": "^2.1.3" + "react-mailchimp-subscribe": "^2.1.3", + "redoc": "^2.0.0", + "styled-components": "^5.3.6" }, "devDependencies": { "@docusaurus/module-type-aliases": "^2.2.0" diff --git a/docs/src/pages/API_relayproxy/index.js b/docs/src/pages/API_relayproxy/index.js index aaa5b296ccf..55f8e15dd7a 100644 --- a/docs/src/pages/API_relayproxy/index.js +++ b/docs/src/pages/API_relayproxy/index.js @@ -7,7 +7,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; export default function redoc() { const {siteConfig} = useDocusaurusContext(); return( - +
Date: Fri, 4 Nov 2022 13:45:48 +0100 Subject: [PATCH 8/9] Use upper case because this is a React page Signed-off-by: Thomas Poignant --- docs/src/pages/API_relayproxy/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/API_relayproxy/index.js b/docs/src/pages/API_relayproxy/index.js index 55f8e15dd7a..c2909925159 100644 --- a/docs/src/pages/API_relayproxy/index.js +++ b/docs/src/pages/API_relayproxy/index.js @@ -4,7 +4,7 @@ import Layout from '@theme/Layout'; import styles from './index.module.css' import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -export default function redoc() { +export default function Redoc() { const {siteConfig} = useDocusaurusContext(); return( From f8ed83d7abe7d5a6e0a6b27cf432068c4719d1b9 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 4 Nov 2022 14:02:09 +0100 Subject: [PATCH 9/9] fix linting issue Signed-off-by: Thomas Poignant --- cmd/relayproxy/controller/health.go | 3 ++- cmd/relayproxy/controller/info.go | 6 ++++-- cmd/relayproxy/main.go | 2 +- cmd/relayproxy/model/user_request.go | 2 +- cmd/relayproxy/modeldocs/eval_docs.go | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/relayproxy/controller/health.go b/cmd/relayproxy/controller/health.go index 76609de255e..31d25c9725d 100644 --- a/cmd/relayproxy/controller/health.go +++ b/cmd/relayproxy/controller/health.go @@ -20,7 +20,8 @@ func NewHealth(monitoring service.Monitoring) Controller { // Handler is the entry point for this API // @Summary Health -// @Description Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve traffic. +// @Description Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve +// @Description traffic. // @Description // @Description This is useful especially for loadbalancer to know that they can send traffic to the service. // @Produce json diff --git a/cmd/relayproxy/controller/info.go b/cmd/relayproxy/controller/info.go index e8ffc1344b9..96d224c0e3e 100644 --- a/cmd/relayproxy/controller/info.go +++ b/cmd/relayproxy/controller/info.go @@ -19,9 +19,11 @@ func NewInfo(monitoring service.Monitoring) Controller { // Handler is the entry point for the Info API // @Summary Info -// @Description Making a **GET** request to the URL path `/info` will tell give you information about the actual state of the relay proxy. +// @Description Making a **GET** request to the URL path `/info` will tell give you information about the actual state +// @Description of the relay proxy. // @Description -// @Description As of Today the level of information is small be we can improve this endpoint to returns more information. +// @Description As of Today the level of information is small be we can improve this endpoint to returns more +// @Description information. // @Produce json // @Success 200 {object} model.InfoResponse // @Router /info [get] diff --git a/cmd/relayproxy/main.go b/cmd/relayproxy/main.go index e2357e20ebf..6bc1f585214 100644 --- a/cmd/relayproxy/main.go +++ b/cmd/relayproxy/main.go @@ -32,7 +32,7 @@ _____________________________________________` // @contact.email contact@gofeatureflag.org // @license.name MIT // @license.url https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE -// @x-logo {"url":"https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png", "altText": "GO Feature Flag logo"} +// @x-logo {"url":"https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png"} // @BasePath / func main() { // Init pFlag for config file diff --git a/cmd/relayproxy/model/user_request.go b/cmd/relayproxy/model/user_request.go index 5ffff51feca..d1abd6ad593 100644 --- a/cmd/relayproxy/model/user_request.go +++ b/cmd/relayproxy/model/user_request.go @@ -20,5 +20,5 @@ type UserRequest struct { Anonymous bool `json:"anonymous" xml:"anonymous" form:"anonymous" query:"anonymous" example:"false"` // Custom is a map containing all extra information for this user. - Custom map[string]interface{} `json:"custom" xml:"custom" form:"custom" query:"custom" swaggertype:"object,string" example:"email:contact@gofeatureflag.org,firstname:John,lastname:Doe,company:GO Feature Flag"` + Custom map[string]interface{} `json:"custom" xml:"custom" form:"custom" query:"custom" swaggertype:"object,string" example:"email:contact@gofeatureflag.org,firstname:John,lastname:Doe,company:GO Feature Flag"` // nolint: lll } diff --git a/cmd/relayproxy/modeldocs/eval_docs.go b/cmd/relayproxy/modeldocs/eval_docs.go index 9ca458f48c6..75b69dd78bc 100644 --- a/cmd/relayproxy/modeldocs/eval_docs.go +++ b/cmd/relayproxy/modeldocs/eval_docs.go @@ -6,7 +6,7 @@ type EvalFlagDoc struct { TrackEvents bool `json:"trackEvents" example:"true"` // The variation used to give you this value. VariationType string `json:"variationType" example:"variation-A"` - //`true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue. + // `true` if something went wrong in the relay proxy (flag does not exists, ...) and we serve the defaultValue. Failed bool `json:"failed" example:"false"` // The version of the flag used. Version string `json:"version" example:"1.0"`