diff --git a/config/legacy-url-mappings.yml b/config/legacy-url-mappings.yml index be726c172..3464dcc36 100644 --- a/config/legacy-url-mappings.yml +++ b/config/legacy-url-mappings.yml @@ -1,82 +1,228 @@ ############################################# # This file defines the legacy URL mappings for the documentation site. # It maps current documentation pages to older versions to ensure users can find the content they need. -# TODO: Refactor the model, for now we just use the first element as current version +# Upon creation of version selection dropdowns, this data is combined with the current version of its product. ############################################# -stack: &stack [ '9.0+', '8.19', '8.18', '8.17', '8.16', '8.15', '8.14', '8.13', '8.12', '8.11', '8.10', '8.9', '8.8', '8.7', '8.6', '8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '7.17' ] +stack: &stack [ '8.19', '8.18', '8.17', '8.16', '8.15', '8.14', '8.13', '8.12', '8.11', '8.10', '8.9', '8.8', '8.7', '8.6', '8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '7.17' ] mappings: - en/apm/agent/android/: [ '1.2.0' , '0.x' ] - en/apm/agent/dotnet/: [ '1.34.0' ] - en/apm/agent/go/: [ '2.7.1', '1.x', '0.5' ] - en/apm/agent/java/: ['1.54.0', '0.7', '0.6'] - en/apm/agent/nodejs/: [ '4.x', '3.x', '2.x', '1.x' ] - en/apm/agent/php/: [ '1.15.1', '1.x' ] - en/apm/agent/python/: [ '6.24.0', '5.x', '4.x', '3.x', '2.x', '1.x' ] - en/apm/agent/ruby/: [ '4.8.0', '3.x', '2.x', '1.x' ] - en/apm/agent/rum-js/: [ '5.17.0', '4.x', '3.x', '2.x', '1.x', '0.x' ] - en/apm/agent/swift/: [ '1.2.1', '0.x' ] - en/apm/attacher/: [ '1.1.3' ] - en/apm/lambda/: [ '1.6.0' ] - en/beats/auditbeat/: *stack - en/beats/devguide/: *stack - en/beats/filebeat/: *stack - en/beats/functionbeat/: *stack - en/beats/heartbeat/: *stack - en/beats/journalbeat/: *stack - en/beats/libbeat/: *stack - en/beats/loggingplugin/: *stack - en/beats/metricbeat/: *stack - en/beats/packetbeat/: *stack - en/beats/topbeat/: *stack - en/beats/winlogbeat/: *stack - en/cloud-heroku/: [] - en/cloud-on-k8s/: [ '3.0+', '2.16', '2.15', '2.14', '2.13', '2.12', '2.11', '2.10', '2.9', '2.8', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] - en/cloud/: [] - en/cloud-enterprise/: [ '4.0', '3.8', '3.7', '3.6', '3.5', '3.4', '3.3', '3.2', '3.1', '3.0', '2.13', '2.12', '2.11', '2.10', '2.9', '2.8', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.1', '1.0' ] - en/ecctl/: [ '1.14+', '1.13', '1.12', '1.11', '1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] - en/ecs-logging/: [] - en/ecs-logging/dotnet/: [ '8.18.1' ] - en/ecs-logging/go-logrus/: [ '1.0.0' ] - en/ecs-logging/go-zap/: [ '1.0.3' ] - en/ecs-logging/go-zerolog/: [ '0.2.0' ] - en/ecs-logging/java/: [ '1.7.0', '0.x' ] - en/ecs-logging/nodejs/: [ '1.5.3' ] - en/ecs-logging/overview/: [] - en/ecs-logging/php/: [ '2.0.0' ] - en/ecs-logging/python/: [ '2.2.0' ] - en/ecs-logging/ruby/: ['1.0.0'] - en/ecs/: [ '9.0+', '8.18', '8.17', '8.16', '8.15', '8.14', '8.13', '8.12', '8.11', '8.10', '8.9', '8.8', '8.7', '8.6', '8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '1.12', '1.11', '1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] - en/elastic-stack-glossary/: [] - en/elastic-stack/: *stack - en/elasticsearch/client/curator/: [ '8.0.21', '7.0', '6.0', '5.8', '5.7', '5.6', '5.5', '5.4', '5.3', '5.2', '5.1', '5.0', '4.3', '4.2', '4.1', '4.0', '3.5', '3.4', '3.3' ] - en/elasticsearch/client/eland/: [ '9.0.1' ] - en/elasticsearch/client/go-api/: *stack - en/elasticsearch/client/java-api-client/: *stack - en/elasticsearch/client/javascript-api/: *stack - en/elasticsearch/client/net-api/: *stack - en/elasticsearch/client/php-api/: *stack - en/elasticsearch/client/python-api/: *stack - en/elasticsearch/client/ruby-api/: *stack - en/elasticsearch/client/rust-api/: *stack - en/elasticsearch/hadoop/: *stack - en/elasticsearch/painless/: *stack - en/elasticsearch/plugins/: *stack - en/elasticsearch/reference/: *stack - en/esf/: ['1.20.1'] - en/fleet/: *stack - en/ingest-overview/: [] - en/ingest/: *stack - en/integrations-developer/: [] - en/integrations/: [] - en/kibana/: *stack - en/logstash-versioned-plugins/: [] - en/logstash/: *stack - en/machine-learning/: *stack - en/observability/: *stack - en/reference-architectures/: [] - en/search-ui/: ['1.24.0'] - en/security/: *stack - en/serverless/: [] - en/starting-with-the-elasticsearch-platform-and-its-solutions/: *stack + en/apm/agent/android/: + product: edot-android + legacy_versions: [ '0.x' ] + en/apm/agent/dotnet/: + product: apm-agent-dotnet + legacy_versions: [] + en/apm/agent/go/: + product: apm-agent-go + legacy_versions: [ '1.x', '0.5' ] + en/apm/agent/java/: + product: apm-agent-java + legacy_versions: [ '0.7', '0.6'] + en/apm/agent/nodejs/: + product: apm-agent-node + legacy_versions: [ '3.x', '2.x', '1.x' ] + en/apm/agent/php/: + product: apm-agent-php + legacy_versions: [] + en/apm/agent/python/: + product: apm-agent-python + legacy_versions: [ '5.x', '4.x', '3.x', '2.x', '1.x' ] + en/apm/agent/ruby/: + product: apm-agent-ruby + legacy_versions: [ '3.x', '2.x', '1.x' ] + en/apm/agent/rum-js/: + product: apm-agent-rum-js + legacy_versions: [ '4.x', '3.x', '2.x', '1.x', '0.x' ] + en/apm/agent/swift/: + product: edot-ios + legacy_versions: [ '0.x' ] + en/apm/attacher/: + product: apm-k8s-attacher + legacy_versions: [] + en/apm/lambda/: + product: apm-aws-lambda + legacy_versions: [] + en/beats/auditbeat/: + product: auditbeat + legacy_versions: *stack + en/beats/devguide/: + product: beats + legacy_versions: *stack + en/beats/filebeat/: + product: filebeat + legacy_versions: *stack + en/beats/functionbeat/: + product: beats + legacy_versions: *stack + en/beats/heartbeat/: + product: heartbeat + legacy_versions: *stack + en/beats/journalbeat/: + product: beats + legacy_versions: *stack + en/beats/libbeat/: + product: beats + legacy_versions: *stack + en/beats/loggingplugin/: + product: beats + legacy_versions: *stack + en/beats/metricbeat/: + product: metricbeat + legacy_versions: *stack + en/beats/packetbeat/: + product: packetbeat + legacy_versions: *stack + en/beats/topbeat/: + product: beats + legacy_versions: *stack + en/beats/winlogbeat/: + product: winlogbeat + legacy_versions: *stack + en/cloud-heroku/: + product: cloud-hosted + legacy_versions: [] + en/cloud-on-k8s/: + product: cloud-kubernetes + legacy_versions: [ '2.16', '2.15', '2.14', '2.13', '2.12', '2.11', '2.10', '2.9', '2.8', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] + en/cloud/: + product: cloud-hosted + legacy_versions: [] + en/cloud-enterprise/: + product: cloud-enterprise + legacy_versions: [ '3.8', '3.7', '3.6', '3.5', '3.4', '3.3', '3.2', '3.1', '3.0', '2.13', '2.12', '2.11', '2.10', '2.9', '2.8', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.1', '1.0' ] + en/ecctl/: + product: cloud-control-ecctl + legacy_versions: [ '1.13', '1.12', '1.11', '1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] + en/ecs-logging/: + product: ecs-logging + legacy_versions: [] + en/ecs-logging/dotnet/: + product: ecs-dotnet + legacy_versions: [] + en/ecs-logging/go-logrus/: + product: ecs-logging-go-logrus + legacy_versions: [] + en/ecs-logging/go-zap/: + product: ecs-logging-go-zap + legacy_versions: [] + en/ecs-logging/go-zerolog/: + product: ecs-logging-go-zerolog + legacy_versions: [] + en/ecs-logging/java/: + product: ecs-logging-java + legacy_versions: ['0.x'] + en/ecs-logging/nodejs/: + product: ecs-logging-nodejs + legacy_versions: [] + en/ecs-logging/overview/: + product: ecs-logging + legacy_versions: [] + en/ecs-logging/php/: + product: ecs-logging-php + legacy_versions: [] + en/ecs-logging/python/: + product: ecs-logging-python + legacy_versions: [] + en/ecs-logging/ruby/: + product: ecs-logging-ruby + legacy_versions: [] + en/ecs/: + product: ecs + legacy_versions: [ '8.18', '8.17', '8.16', '8.15', '8.14', '8.13', '8.12', '8.11', '8.10', '8.9', '8.8', '8.7', '8.6', '8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '1.12', '1.11', '1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0' ] + en/elastic-stack-glossary/: + product: elastic-stack + legacy_versions: [] + en/elastic-stack/: + product: elastic-stack + legacy_versions: *stack + en/elasticsearch/client/curator/: + product: curator + legacy_versions: [ '7.0', '6.0', '5.8', '5.7', '5.6', '5.5', '5.4', '5.3', '5.2', '5.1', '5.0', '4.3', '4.2', '4.1', '4.0', '3.5', '3.4', '3.3' ] + en/elasticsearch/client/eland/: + product: eland + legacy_versions: [] + en/elasticsearch/client/go-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/java-api-client/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/javascript-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/net-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/php-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/python-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/ruby-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/client/rust-api/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/hadoop/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/painless/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/plugins/: + product: elasticsearch + legacy_versions: *stack + en/elasticsearch/reference/: + product: elasticsearch + legacy_versions: *stack + en/esf/: + product: elastic-serverless-forwarder + legacy_versions: [] + en/fleet/: + product: elastic-agent + legacy_versions: *stack + en/ingest-overview/: + product: elastic-stack + legacy_versions: [] + en/ingest/: + product: elastic-stack + legacy_versions: *stack + en/integrations-developer/: + product: integrations + legacy_versions: [] + en/integrations/: + product: integrations + legacy_versions: [] + en/kibana/: + product: kibana + legacy_versions: *stack + en/logstash-versioned-plugins/: + product: logstash + legacy_versions: [] + en/logstash/: + product: logstash + legacy_versions: *stack + en/machine-learning/: + product: elasticsearch + legacy_versions: *stack + en/observability/: + product: observability + legacy_versions: *stack + en/reference-architectures/: + product: elasticsearch + legacy_versions: [] + en/search-ui/: + product: search-ui + legacy_versions: [] + en/security/: + product: security + legacy_versions: *stack + en/serverless/: + product: cloud-serverless + legacy_versions: [] + en/starting-with-the-elasticsearch-platform-and-its-solutions/: + product: elastic-stack + legacy_versions: *stack diff --git a/config/products.yml b/config/products.yml new file mode 100644 index 000000000..0a35f5dd9 --- /dev/null +++ b/config/products.yml @@ -0,0 +1,184 @@ +products: + apm: + display: 'APM' + versioning: 'stack' + apm-agent: + display: 'APM Agent' + versioning: 'all' + apm-agent-dotnet: + display: 'APM .NET Agent' + apm-agent-go: + display: 'APM Go Agent' + apm-agent-java: + display: 'APM Java Agent' + apm-agent-node: + display: 'APM Node.js Agent' + apm-agent-php: + display: 'APM PHP Agent' + apm-agent-python: + display: 'APM Python Agent' + apm-agent-ruby: + display: 'APM Ruby Agent' + apm-agent-rum-js: + display: 'APM RUM JavaScript Agent' + apm-k8s-attacher: + display: 'APM Attacher for Kubernetes' + versioning: 'apm-attacher' + apm-aws-lambda: + display: 'APM AWS Lambda extension' + versioning: 'apm-lambda' + apm-server: + display: 'APM Server' + versioning: 'stack' + auditbeat: + display: 'Auditbeat' + versioning: 'stack' + beats: + display: 'Beats' + versioning: 'stack' + cloud-control-ecctl: + display: 'Elastic Cloud Control' + versioning: 'ecctl' + cloud-enterprise: + display: 'Elastic Cloud Enterprise' + versioning: 'ece' + cloud-hosted: + display: 'Elastic Cloud Hosted' + versioning: 'ech' + cloud-kubernetes: + display: 'Elastic Cloud on Kubernetes' + versioning: 'eck' + cloud-serverless: + display: 'Elastic Cloud Serverless' + versioning: 'serverless' + cloud-terraform: + display: 'Elastic Cloud Terraform Provider' + versioning: 'cloud-terraform' + curator: + display: 'Elasticsearch Curator' + ecs: + display: 'Elastic Common Schema (ECS)' + ecs-logging: + display: 'ECS Logging' + versioning: 'stack' + ecs-dotnet: + display: 'ECS Logging .NET' + versioning: 'ecs-logging-dotnet' + ecs-logging-go-logrus: + display: 'ECS Logging Go (Logrus)' + ecs-logging-go-zap: + display: 'ECS Logging Go (Zap)' + ecs-logging-go-zerolog: + display: 'ECS Logging Go (Zerolog)' + ecs-logging-java: + display: 'ECS Logging Java' + ecs-logging-nodejs: + display: 'ECS Logging Node.js' + ecs-logging-php: + display: 'ECS Logging PHP' + ecs-logging-python: + display: 'ECS Logging Python' + ecs-logging-ruby: + display: 'ECS Logging Ruby' + edot-cf: + display: 'EDOT Cloud Forwarder' + versioning: 'stack' + edot-sdk: + display: 'Elastic Distribution of OpenTelemetry SDK' + versioning: 'all' + edot-collector: + display: 'Elastic Distribution of OpenTelemetry Collector' + versioning: 'stack' + edot-ios: + display: 'Elastic Distribution of OpenTelemetry iOS' + edot-android: + display: 'Elastic Distribution of OpenTelemetry Android' + edot-dotnet: + display: 'Elastic Distribution of OpenTelemetry .NET' + edot-java: + display: 'Elastic Distribution of OpenTelemetry Java' + edot-node: + display: 'Elastic Distribution of OpenTelemetry Node' + edot-php: + display: 'Elastic Distribution of OpenTelemetry PHP' + edot-python: + display: 'Elastic Distribution of OpenTelemetry Python' + edot-cf-aws: + display: 'EDOT Cloud Forwarder for AWS' + edot-cf-azure: + display: 'EDOT Cloud Forwarder for Azure' + eland: + display: 'Eland' + versioning: 'stack' + elastic-agent: + display: 'Elastic Agent' + versioning: 'stack' + elastic-serverless-forwarder: + display: 'Elastic Serverless Forwarder' + versioning: 'esf' + elastic-stack: + display: 'Elastic Stack' + versioning: 'stack' + elasticsearch: + display: 'Elasticsearch' + versioning: 'stack' + elasticsearch-client: + display: 'Elasticsearch Client' + versioning: 'stack' + ess: + display: 'Elastic Cloud Hosted' + versioning: 'all' + filebeat: + display: 'Filebeat' + versioning: 'stack' + fleet: + display: 'Fleet' + versioning: 'stack' + heartbeat: + display: 'Heartbeat' + versioning: 'stack' + integrations: + display: 'Elastic integrations' + versioning: 'stack' + kibana: + display: 'Kibana' + versioning: 'stack' + logstash: + display: 'Logstash' + versioning: 'stack' + machine-learning: + display: 'Machine Learning' + versioning: 'stack' + metricbeat: + display: 'Metricbeat' + versioning: 'stack' + observability: + display: 'Elastic Observability' + versioning: 'stack' + packetbeat: + display: 'Packetbeat' + versioning: 'stack' + painless: + display: 'Painless' + versioning: 'stack' + search-ui: + display: 'Search UI' + versioning: 'stack' + security: + display: 'Elastic Security' + versioning: 'stack' + self: + display: 'Self-managed Elastic' + versioning: 'stack' + serverless-elasticsearch: + display: 'Elasticsearch Serverless' + versioning: 'all' + serverless-observability: + display: 'Elastic Observability Serverless' + versioning: 'all' + serverless-security: + display: 'Elastic Security Serverless' + versioning: 'all' + winlogbeat: + display: 'Winlogbeat' + versioning: 'stack' \ No newline at end of file diff --git a/config/versions.yml b/config/versions.yml index bd9e80766..72cf7be84 100644 --- a/config/versions.yml +++ b/config/versions.yml @@ -21,6 +21,9 @@ versioning_systems: base: 3.0 current: 3.1.0 ess: *all + ecs: + base: 9.0 + current: 9.1.0 self: *stack ecctl: base: 1.0 @@ -36,62 +39,105 @@ versioning_systems: security: *all # APM agents - # apm_agent_android: - # base: 1.0 - # current: 1.0.0 - apm_agent_dotnet: + apm-agent-dotnet: base: 1.0 current: 1.34.1 - apm_agent_go: + apm-agent-go: base: 2.0 current: 2.7.1 - apm_agent_java: + apm-agent-java: base: 1.0 current: 1.55.1 - apm_agent_node: + apm-agent-node: base: 4.0 current: 4.13.0 - apm_agent_php: + apm-agent-php: base: 1.0 current: 1.15.1 - apm_agent_python: + apm-agent-python: base: 6.0 current: 6.24.0 - apm_agent_ruby: + apm-agent-ruby: base: 4.0 current: 4.8.0 - apm_agent_rum: + apm-agent-rum-js: base: 5.0 current: 5.17.0 + apm-attacher: + base: 1.0 + current: 1.1.3 + apm-lambda: + base: 1.0 + current: 1.6.0 # EDOTs - edot_collector: + edot-collector: base: 9.0 current: 9.1.4 - edot_ios: + edot-ios: base: 1.0 current: 1.3.0 - edot_android: + edot-android: base: 1.0 current: 1.2.0 - edot_dotnet: + edot-dotnet: base: 1.0 current: 1.1.0 - edot_java: + edot-java: base: 1.0 current: 1.5.0 - edot_node: + edot-node: base: 1.0 current: 1.4.0 - edot_php: + edot-php: base: 1.0 current: 1.1.1 - edot_python: + edot-python: base: 1.0 current: 1.8.0 - edot_cf_aws: + edot-cf-aws: base: 0.1 current: 0.2.0 - edot_cf_azure: + edot-cf-azure: base: 0.1 - current: 0.6.0 \ No newline at end of file + current: 0.6.0 + + # Logging + ecs-logging-dotnet: + base: 8.0 + current: 8.18.1 + ecs-logging-go-logrus: + base: 1.0 + current: 1.0.0 + ecs-logging-go-zap: + base: 1.0 + current: 1.0.3 + ecs-logging-go-zerolog: + base: 0.2 + current: 0.2.0 + ecs-logging-java: + base: 1.0 + current: 1.7.0 + ecs-logging-nodejs: + base: 1.0 + current: 1.5.3 + ecs-logging-php: + base: 2.0 + current: 2.0.0 + ecs-logging-python: + base: 2.0 + current: 2.2.0 + ecs-logging-ruby: + base: 1.0 + current: 1.0.0 + esf: + base: 1.0 + current: 1.21.0 + search-ui: + base: 1.0 + current: 1.24.2 + +# Other + cloud-terraform: + base: 0.11 + current: 0.11.17 diff --git a/docs-builder.sln b/docs-builder.sln index af146cc5e..51348c58e 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -121,6 +121,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{6FAB56 config\assembler.yml = config\assembler.yml config\legacy-url-mappings.yml = config\legacy-url-mappings.yml config\navigation.yml = config\navigation.yml + config\products.yml = config\products.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests-integration", "tests-integration", "{BCAD38D5-6C83-46E2-8398-4BE463931098}" diff --git a/docs/_docset.yml b/docs/_docset.yml index bf25daf6f..dd11ae52c 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -76,6 +76,7 @@ toc: - file: navigation.md - file: versions.md - file: legacy-url-mappings.md + - file: products.md - folder: content-set children: - file: index.md diff --git a/docs/configure/site/index.md b/docs/configure/site/index.md index 1728bad3e..e255e46a2 100644 --- a/docs/configure/site/index.md +++ b/docs/configure/site/index.md @@ -10,6 +10,7 @@ Configure the documentation site in these files: - [](./navigation.md) - global navigation index - [](./versions.md) - global versioning schemes - [](./legacy-url-mappings.md) - supported legacy version +- [](./products.md) - company product metadata ## Redirects diff --git a/docs/configure/site/legacy-url-mappings.md b/docs/configure/site/legacy-url-mappings.md index 11ab0a221..0d72bb433 100644 --- a/docs/configure/site/legacy-url-mappings.md +++ b/docs/configure/site/legacy-url-mappings.md @@ -5,7 +5,9 @@ This [`legacy-url-mappings.yml`](https://github.com/elastic/docs-builder/blob/ma This example maps documentation that references `elastic.co/guide/en/elasticsearch/reference/ to Elastic Stack versioned URL paths: ```yml -en/elasticsearch/reference/: *stack +en/elasticsearch/reference/: + product: elastic-stack + legacy_versions: *stack ``` ## Structure @@ -14,11 +16,7 @@ en/elasticsearch/reference/: *stack : Defines a reusable list of version strings for "stack" projects, e.g., [ '9.0+', '8.18', ... ]. `mappings` -: A YAML mapping where each key is a legacy documentation URL path (like `en/apm/agent/java/`) and the value is a list of asciidoc versions that exist for that path. - -:::{important} -The first version in the `mappings` list is treated as the "current" version in documentation version dropdown. -::: - -## Example entry +: A YAML mapping where each key is a legacy documentation URL path (like `en/apm/agent/java/`), and each value is a mapping with: +* `product`: Specifies the product or project type (e.g., `elastic-stack`, `eck`, `ece`, `self-managed`, etc.). Products must be defined in [`products.yml`](https://github.com/elastic/docs-builder/blob/main/config/products.yml). See [products.yml](./products.md) for more information. +* `legacy_versions`: A list of version strings that correspond to the available asciidoc pages. diff --git a/docs/configure/site/products.md b/docs/configure/site/products.md new file mode 100644 index 000000000..9a5ca5164 --- /dev/null +++ b/docs/configure/site/products.md @@ -0,0 +1,49 @@ +# `products.yml` + +The [`products.yml`](https://github.com/elastic/docs-builder/blob/main/config/products.yml) file specifies metadata regarding the projects in the organization that use the V3 docs system. + +```yml +products: + apm-agent-dotnet: + display: 'APM .NET Agent' + versioning: 'apm-agent-dotnet' + edot-collector: + display: 'Elastic Distribution of OpenTelemetry Collector' + versioning: 'stack' +#... +``` + +## Structure + +`products` +: A YAML mapping where each key is an Elastic product. +* `display`: A friendly name for the product. +* `versioning`: The versioning system used by the project. The value for this field must match one of the versioning systems defined in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml) + + + +## Substitutions + +Writing `{{ product. }}` renders the friendly name of the product in the documentation. For example: + +| Substitution | Result | +|---------------------------------|---| +| `{{ product.apm-agent-dotnet }}` |{{ product.apm-agent-dotnet }} | +| `{{ product.edot-collector }}` | {{ product.edot-collector }} | + +You can also use the shorthand notation `{{ . }}`. For example: + +| Substitution | Result | +|---------------------------------|---| +| `{{ .apm-agent-dotnet }}` |{{ .apm-agent-dotnet }} | +| `{{ .edot-collector }}` | {{ .edot-collector }} | + + +:::{note} +While the recommended separator is a hyphen (`-`) to promote cohesion, underscores (`_`) are also supported, and internally read as hyphens. +::: + +## See also + +[](./versions.md) +[](./legacy-url-mappings.md) diff --git a/docs/syntax/substitutions.md b/docs/syntax/substitutions.md index 3e7624e69..45f5315f5 100644 --- a/docs/syntax/substitutions.md +++ b/docs/syntax/substitutions.md @@ -36,11 +36,13 @@ To use the variables in your files, surround them in curly brackets (`{{variable Here are some variable substitutions: -| Variable | Defined in | -|-----------------------|--------------| -| {{frontmatter_key}} | Front Matter | -| {{a-key-with-dashes}} | Front Matter | -| {{a-global-variable}} | `docset.yml` | +| Variable | Defined in | +|-----------------------|----------------| +| {{frontmatter_key}} | Front Matter | +| {{a-key-with-dashes}} | Front Matter | +| {{a-global-variable}} | `docset.yml` | +| {{product.kibana}} | `products.yml` | +| {{.kibana}} | `products.yml` | ## Mutations @@ -217,3 +219,20 @@ With mutations: {subs=true}`version {{version.stack | M.M}}` :::{note} Regular inline code (without the `{subs}` role) will not process substitutions and will display the variable placeholders as-is. ::: + +## Products + +Product substitutions use `products.yml` to determine what will be displayed. Use the product's key to get its display name. + +```yaml +# products.yml + +id: + display: {value} + ... +# example: + apm-agent-dotnet: + display: 'APM .NET Agent' +``` + +A shorthand format is also available. Using `{{.id}}` is equivalent to `{{product.id}}`. diff --git a/docs/syntax/version-variables.md b/docs/syntax/version-variables.md index 838b36ca2..f841d63a6 100644 --- a/docs/syntax/version-variables.md +++ b/docs/syntax/version-variables.md @@ -82,6 +82,8 @@ This is dictated by the [`versions.yml`](https://github.com/elastic/docs-builder * `ech` * `eck` * `ess` +* `esf` +* `search_ui` * `self` * `ecctl` * `curator` @@ -106,6 +108,17 @@ This is dictated by the [`versions.yml`](https://github.com/elastic/docs-builder * `edot_cf_aws` * `edot_cf_azure` * `edot_collector` +* `apm_attacher` +* `apm_lambda` +* `ecs_logging_dotnet` +* `ecs_logging_go_logrus` +* `ecs_logging_go_zap` +* `ecs_logging_go_zerolog` +* `ecs_logging_java` +* `ecs_logging_nodejs` +* `ecs_logging_php` +* `ecs_logging_python` +* `ecs_logging_ruby` The following are available but should not be used. These map to serverless projects and have a fixed high version number. diff --git a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs index f9ac707fc..3cccb9cf2 100644 --- a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs @@ -18,13 +18,9 @@ public static AssemblyConfiguration Deserialize(string yaml, bool skipPrivateRep { var input = new StringReader(yaml); - var deserializer = new StaticDeserializerBuilder(new YamlStaticContext()) - .IgnoreUnmatchedProperties() - .Build(); - try { - var config = deserializer.Deserialize(input); + var config = ConfigurationFileProvider.Deserializer.Deserialize(input); foreach (var (name, r) in config.ReferenceRepositories) { var repository = RepositoryDefaults(r, name); diff --git a/src/Elastic.Documentation.Configuration/BuildContext.cs b/src/Elastic.Documentation.Configuration/BuildContext.cs index 9e78846fe..6b3b383c3 100644 --- a/src/Elastic.Documentation.Configuration/BuildContext.cs +++ b/src/Elastic.Documentation.Configuration/BuildContext.cs @@ -6,6 +6,8 @@ using System.Reflection; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; @@ -30,6 +32,9 @@ public record BuildContext : IDocumentationSetContext, IDocumentationConfigurati public ConfigurationFileProvider ConfigurationFileProvider { get; } public DocumentationEndpoints Endpoints { get; } + public ProductsConfiguration ProductsConfiguration { get; } + public LegacyUrlMappingConfiguration LegacyUrlMappings { get; } + public IFileInfo ConfigurationPath { get; } public GitCheckoutInformation Git { get; } @@ -82,6 +87,8 @@ public BuildContext( AvailableExporters = availableExporters; VersionsConfiguration = configurationContext.VersionsConfiguration; ConfigurationFileProvider = configurationContext.ConfigurationFileProvider; + ProductsConfiguration = configurationContext.ProductsConfiguration; + LegacyUrlMappings = configurationContext.LegacyUrlMappings; Endpoints = configurationContext.Endpoints; var rootFolder = !string.IsNullOrWhiteSpace(source) @@ -100,7 +107,7 @@ public BuildContext( DocumentationSourceDirectory = ConfigurationPath.Directory!; Git = gitCheckoutInformation ?? GitCheckoutInformation.Create(DocumentationCheckoutDirectory, ReadFileSystem); - Configuration = new ConfigurationFile(this, VersionsConfiguration); + Configuration = new ConfigurationFile(this, VersionsConfiguration, ProductsConfiguration); GoogleTagManager = new GoogleTagManagerConfiguration { Enabled = false diff --git a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs index 3529782a1..a5b1c94de 100644 --- a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs +++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using DotNet.Globbing; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Suggestions; using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Configuration.Versions; @@ -36,7 +37,7 @@ public record ConfigurationFile : ITableOfContentsScope public Dictionary? Redirects { get; } - public HashSet Products { get; } = new(StringComparer.Ordinal); + public HashSet Products { get; } = []; public HashSet ImplicitFolders { get; } = new(StringComparer.OrdinalIgnoreCase); @@ -62,7 +63,7 @@ public record ConfigurationFile : ITableOfContentsScope Project is not null && Project.Equals("Elastic documentation", StringComparison.OrdinalIgnoreCase); - public ConfigurationFile(IDocumentationSetContext context, VersionsConfiguration versionsConfig) + public ConfigurationFile(IDocumentationSetContext context, VersionsConfiguration versionsConfig, ProductsConfiguration productsConfig) { _context = context; ScopeDirectory = context.ConfigurationPath.Directory!; @@ -133,6 +134,7 @@ public ConfigurationFile(IDocumentationSetContext context, VersionsConfiguration foreach (var node in sequence.Children.OfType()) { YamlScalarNode? productId = null; + foreach (var child in node.Children) { if (child is { Key: YamlScalarNode { Value: "id" }, Value: YamlScalarNode scalarNode }) @@ -147,10 +149,10 @@ public ConfigurationFile(IDocumentationSetContext context, VersionsConfiguration break; } - if (!Builder.Products.AllById.ContainsKey(productId.Value)) - reader.EmitError($"Product \"{productId.Value}\" not found in the product list. {new Suggestion(Builder.Products.All.Select(p => p.Id).ToHashSet(), productId.Value).GetSuggestionQuestion()}", node); + if (!productsConfig.Products.TryGetValue(productId.Value.Replace('_', '-'), out var productToAdd)) + reader.EmitError($"Product \"{productId.Value}\" not found in the product list. {new Suggestion(productsConfig.Products.Select(p => p.Value.Id).ToHashSet(), productId.Value).GetSuggestionQuestion()}", node); else - _ = Products.Add(productId.Value); + _ = Products.Add(productToAdd); } break; case "features": @@ -168,11 +170,20 @@ public ConfigurationFile(IDocumentationSetContext context, VersionsConfiguration foreach (var (id, system) in versionsConfig.VersioningSystems) { var name = id.ToStringFast(true); - var key = $"version.{name}"; - _substitutions[key] = system.Current; + var alternativeName = name.Replace('-', '_'); + _substitutions[$"version.{name}"] = system.Current; + _substitutions[$"version.{alternativeName}"] = system.Current; + _substitutions[$"version.{name}.base"] = system.Base; + _substitutions[$"version.{alternativeName}.base"] = system.Base; + } - key = $"version.{name}.base"; - _substitutions[key] = system.Base; + foreach (var product in productsConfig.Products.Values) + { + var alternativeProductId = product.Id.Replace('-', '_'); + _substitutions[$"product.{product.Id}"] = product.DisplayName; + _substitutions[$".{product.Id}"] = product.DisplayName; + _substitutions[$"product.{alternativeProductId}"] = product.DisplayName; + _substitutions[$".{alternativeProductId}"] = product.DisplayName; } var toc = new TableOfContentsConfiguration(this, sourceFile, ScopeDirectory, _context, 0, ""); diff --git a/src/Elastic.Documentation.Configuration/Builder/Products.cs b/src/Elastic.Documentation.Configuration/Builder/Products.cs deleted file mode 100644 index 86208364c..000000000 --- a/src/Elastic.Documentation.Configuration/Builder/Products.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.Collections.Frozen; - -namespace Elastic.Documentation.Configuration.Builder; - -public record Product(string Id, string DisplayName); - -public static class Products -{ - public static FrozenSet All { get; } = [ - new("apm", "APM"), - new("apm-agent", "APM Agent"), - new("auditbeat", "Auditbeat"), - new("beats", "Beats"), - new("cloud-control-ecctl", "Elastic Cloud Control ECCTL"), - new("cloud-enterprise", "Elastic Cloud Enterprise"), - new("cloud-hosted", "Elastic Cloud Hosted"), - new("cloud-kubernetes", "Elastic Cloud Kubernetes"), - new("cloud-serverless", "Elastic Cloud Serverless"), - new("cloud-terraform", "Elastic Cloud Terraform"), - new("ecs", "Elastic Common Schema (ECS)"), - new("ecs-logging", "ECS Logging"), - new("edot-cf", "EDOT Cloud Forwarder"), - new("edot-sdk", "Elastic Distribution of OpenTelemetry SDK"), - new("edot-collector", "Elastic Distribution of OpenTelemetry Collector"), - new("elastic-agent", "Elastic Agent"), - new("elastic-serverless-forwarder", "Elastic Serverless Forwarder"), - new("elastic-stack", "Elastic Stack"), - new("elasticsearch", "Elasticsearch"), - new("elasticsearch-client", "Elasticsearch Client"), - new("filebeat", "Filebeat"), - new("fleet", "Fleet"), - new("heartbeat", "Heartbeat"), - new("integrations", "Integrations"), - new("kibana", "Kibana"), - new("logstash", "Logstash"), - new("machine-learning", "Machine Learning"), - new("metricbeat", "Metricbeat"), - new("observability", "Elastic Observability"), - new("packetbeat", "Packetbeat"), - new("painless", "Elasticsearch Painless scripting language"), - new("search-ui", "Search UI"), - new("security", "Elastic Security"), - new("winlogbeat", "Winlogbeat"), - ]; - - public static FrozenDictionary AllById { get; } = All.ToDictionary(p => p.Id, StringComparer.Ordinal).ToFrozenDictionary(); -} diff --git a/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs index 8e96b4fc2..58c06111d 100644 --- a/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs +++ b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs @@ -5,8 +5,11 @@ using System.IO.Abstractions; using System.Text.RegularExpressions; using Elastic.Documentation.Configuration.Assembler; +using Elastic.Documentation.Configuration.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace Elastic.Documentation.Configuration; @@ -16,7 +19,12 @@ public partial class ConfigurationFileProvider private readonly string _assemblyName; private readonly ILogger _logger; + internal static IDeserializer Deserializer { get; } = new StaticDeserializerBuilder(new YamlStaticContext()) + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + public ConfigurationSource ConfigurationSource { get; } + public string? GitReference { get; } public ConfigurationFileProvider( @@ -73,6 +81,7 @@ public ConfigurationFileProvider( } VersionFile = CreateTemporaryConfigurationFile("versions.yml"); + ProductsFile = CreateTemporaryConfigurationFile("products.yml"); AssemblerFile = CreateTemporaryConfigurationFile("assembler.yml"); NavigationFile = CreateTemporaryConfigurationFile("navigation.yml"); LegacyUrlMappingsFile = CreateTemporaryConfigurationFile("legacy-url-mappings.yml"); @@ -86,6 +95,8 @@ public ConfigurationFileProvider( public IFileInfo VersionFile { get; } + public IFileInfo ProductsFile { get; } + public IFileInfo AssemblerFile { get; } public IFileInfo LegacyUrlMappingsFile { get; } diff --git a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj index 551c2af0a..c7defd0a3 100644 --- a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj +++ b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Elastic.Documentation.Configuration/IDocumentationConfigurationContext.cs b/src/Elastic.Documentation.Configuration/IDocumentationConfigurationContext.cs index da15a7635..4a44881cc 100644 --- a/src/Elastic.Documentation.Configuration/IDocumentationConfigurationContext.cs +++ b/src/Elastic.Documentation.Configuration/IDocumentationConfigurationContext.cs @@ -2,6 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; namespace Elastic.Documentation.Configuration; @@ -11,6 +13,8 @@ public interface IConfigurationContext VersionsConfiguration VersionsConfiguration { get; } ConfigurationFileProvider ConfigurationFileProvider { get; } DocumentationEndpoints Endpoints { get; } + ProductsConfiguration ProductsConfiguration { get; } + LegacyUrlMappingConfiguration LegacyUrlMappings { get; } } /// Used only to seed in DI, you primarily want to depend on @@ -24,6 +28,13 @@ public class ConfigurationContext : IConfigurationContext /// public required DocumentationEndpoints Endpoints { get; init; } + + /// + public required ProductsConfiguration ProductsConfiguration { get; init; } + + /// + public required LegacyUrlMappingConfiguration LegacyUrlMappings { get; init; } + } public interface IDocumentationConfigurationContext : IDocumentationContext, IConfigurationContext; diff --git a/src/Elastic.Documentation/Legacy/ILegacyUrlMapper.cs b/src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMapping.cs similarity index 52% rename from src/Elastic.Documentation/Legacy/ILegacyUrlMapper.cs rename to src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMapping.cs index 390d7d3eb..dd815df6f 100644 --- a/src/Elastic.Documentation/Legacy/ILegacyUrlMapper.cs +++ b/src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMapping.cs @@ -2,12 +2,25 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -namespace Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.Products; -public record LegacyPageMapping(string RawUrl, string Version, bool Exists) +namespace Elastic.Documentation.Configuration.LegacyUrlMappings; + +public record LegacyUrlMappingConfiguration +{ + public required IReadOnlyCollection Mappings { get; init; } +} +public record LegacyUrlMapping +{ + public required string BaseUrl { get; init; } + public required Product Product { get; init; } + public required IReadOnlyCollection LegacyVersions { get; init; } +} + +public record LegacyPageMapping(Product Product, string RawUrl, string Version, bool Exists) { public override string ToString() => RawUrl.Replace("/current/", $"/{Version}/"); -}; +} public interface ILegacyUrlMapper { diff --git a/src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMappingExtensions.cs b/src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMappingExtensions.cs new file mode 100644 index 000000000..07760d8ed --- /dev/null +++ b/src/Elastic.Documentation.Configuration/LegacyUrlMappings/LegacyUrlMappingExtensions.cs @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Immutable; +using Elastic.Documentation.Configuration.Products; + +namespace Elastic.Documentation.Configuration.LegacyUrlMappings; + +public static class LegacyUrlMappingExtensions +{ + public static LegacyUrlMappingConfiguration CreateLegacyUrlMappings(this ConfigurationFileProvider provider, ProductsConfiguration products) + { + var legacyUrlMappingsFilePath = provider.LegacyUrlMappingsFile; + + var legacyUrlMappingsDto = ConfigurationFileProvider.Deserializer.Deserialize(legacyUrlMappingsFilePath.OpenText()); + + var legacyUrlMappings = legacyUrlMappingsDto.Mappings.Select(kvp => + new LegacyUrlMapping + { + BaseUrl = kvp.Key, + Product = products.Products[kvp.Value.Product], + LegacyVersions = kvp.Value.LegacyVersions.ToImmutableList() + }); + + return new LegacyUrlMappingConfiguration { Mappings = legacyUrlMappings.ToImmutableList() }; + } +} + +// Private DTOs for deserialization. These match the YAML structure directly. + +internal sealed record LegacyUrlMappingConfigDto +{ + public IEnumerable Stack { get; set; } = []; + public Dictionary Mappings { get; set; } = []; +} + +internal sealed record LegacyUrlMappingDto +{ + public string Product { get; set; } = string.Empty; + public IEnumerable LegacyVersions { get; set; } = []; +} diff --git a/src/Elastic.Documentation.Configuration/Products/Product.cs b/src/Elastic.Documentation.Configuration/Products/Product.cs new file mode 100644 index 000000000..9dabeb1eb --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Products/Product.cs @@ -0,0 +1,23 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Frozen; +using Elastic.Documentation.Configuration.Versions; +using YamlDotNet.Serialization; + +namespace Elastic.Documentation.Configuration.Products; + +public record ProductsConfiguration +{ + public required FrozenDictionary Products { get; init; } +} + +[YamlSerializable] +public record Product +{ + public required string Id { get; init; } + public required string DisplayName { get; init; } + public VersioningSystem? VersioningSystem { get; init; } +} + diff --git a/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs new file mode 100644 index 000000000..eab68d0bb --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs @@ -0,0 +1,44 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Frozen; +using Elastic.Documentation.Configuration.Versions; + +namespace Elastic.Documentation.Configuration.Products; + +public static class ProductExtensions +{ + public static ProductsConfiguration CreateProducts(this ConfigurationFileProvider provider, VersionsConfiguration versionsConfiguration) + { + var productsFilePath = provider.ProductsFile; + + var productsDto = ConfigurationFileProvider.Deserializer.Deserialize(productsFilePath.OpenText()); + + var products = productsDto.Products.ToDictionary( + kvp => kvp.Key, + kvp => new Product + { + Id = kvp.Key, + DisplayName = kvp.Value.Display, + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)) + }); + + return new ProductsConfiguration + { + Products = products.ToFrozenDictionary() + }; + } +} + +// Private DTOs for deserialization. These match the YAML structure directly. + +internal sealed record ProductConfigDto +{ + public Dictionary Products { get; set; } = []; +} +internal sealed record ProductDto +{ + public string Display { get; set; } = string.Empty; + public string? Versioning { get; set; } +} diff --git a/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs b/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs index 3bc8769c0..5a9c6ef8a 100644 --- a/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs +++ b/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information using Elastic.Documentation.Configuration.Assembler; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using YamlDotNet.Serialization; @@ -16,5 +18,9 @@ namespace Elastic.Documentation.Configuration.Serialization; [YamlSerializable(typeof(GoogleTagManager))] [YamlSerializable(typeof(ContentSource))] [YamlSerializable(typeof(VersionsConfigDto))] +[YamlSerializable(typeof(ProductConfigDto))] [YamlSerializable(typeof(VersioningSystemDto))] +[YamlSerializable(typeof(ProductDto))] +[YamlSerializable(typeof(LegacyUrlMappingDto))] +[YamlSerializable(typeof(LegacyUrlMappingConfigDto))] public partial class YamlStaticContext; diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs b/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs index 4d5571a00..bdb1c50f6 100644 --- a/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs @@ -35,6 +35,8 @@ public enum VersioningSystemId Eck, [Display(Name = "ess")] Ess, + [Display(Name = "ecs")] + Ecs, [Display(Name = "self")] Self, [Display(Name = "ecctl")] @@ -49,46 +51,74 @@ public enum VersioningSystemId ObservabilityProject, [Display(Name = "security")] SecurityProject, - [Display(Name = "apm_agent_android")] + [Display(Name = "apm-agent-android")] ApmAgentAndroid, - [Display(Name = "apm_agent_ios")] + [Display(Name = "apm-agent-ios")] ApmAgentIos, - [Display(Name = "apm_agent_dotnet")] + [Display(Name = "apm-agent-dotnet")] ApmAgentDotnet, - [Display(Name = "apm_agent_go")] + [Display(Name = "apm-agent-go")] ApmAgentGo, - [Display(Name = "apm_agent_java")] + [Display(Name = "apm-agent-java")] ApmAgentJava, - [Display(Name = "apm_agent_node")] + [Display(Name = "apm-agent-node")] ApmAgentNode, - [Display(Name = "apm_agent_php")] + [Display(Name = "apm-agent-php")] ApmAgentPhp, - [Display(Name = "apm_agent_python")] + [Display(Name = "apm-agent-python")] ApmAgentPython, - [Display(Name = "apm_agent_ruby")] + [Display(Name = "apm-agent-ruby")] ApmAgentRuby, - [Display(Name = "apm_agent_rum")] - ApmAgentRum, - [Display(Name = "edot_ios")] + [Display(Name = "apm-agent-rum-js")] + ApmAgentRumJs, + [Display(Name = "apm-attacher")] + ApmAttacher, + [Display(Name = "apm-lambda")] + ApmLambda, + [Display(Name = "ecs-logging-dotnet")] + EcsLoggingDotnet, + [Display(Name = "ecs-logging-go-logrus")] + EcsLoggingGoLogrus, + [Display(Name = "ecs-logging-go-zap")] + EcsLoggingGoZap, + [Display(Name = "ecs-logging-go-zerolog")] + EcsLoggingGoZerolog, + [Display(Name = "ecs-logging-java")] + EcsLoggingJava, + [Display(Name = "ecs-logging-nodejs")] + EcsLoggingNodeJs, + [Display(Name = "ecs-logging-php")] + EcsLoggingPhp, + [Display(Name = "ecs-logging-python")] + EcsLoggingPython, + [Display(Name = "ecs-logging-ruby")] + EcsLoggingRuby, + [Display(Name = "esf")] + Esf, + [Display(Name = "edot-ios")] EdotIos, - [Display(Name = "edot_android")] + [Display(Name = "edot-android")] EdotAndroid, - [Display(Name = "edot_dotnet")] + [Display(Name = "edot-dotnet")] EdotDotnet, - [Display(Name = "edot_java")] + [Display(Name = "edot-java")] EdotJava, - [Display(Name = "edot_node")] + [Display(Name = "edot-node")] EdotNode, - [Display(Name = "edot_php")] + [Display(Name = "edot-php")] EdotPhp, - [Display(Name = "edot_python")] + [Display(Name = "edot-python")] EdotPython, - [Display(Name = "edot_cf_aws")] + [Display(Name = "edot-cf-aws")] EdotCfAws, - [Display(Name = "edot_cf_azure")] + [Display(Name = "edot-cf-azure")] EdotCfAzure, - [Display(Name = "edot_collector")] - EdotCollector + [Display(Name = "edot-collector")] + EdotCollector, + [Display(Name = "search-ui")] + SearchUI, + [Display(Name = "cloud-terraform")] + CloudTerraform, } [YamlSerializable] diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs b/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs index 25c0f2f0f..00ae91b88 100644 --- a/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs +++ b/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs @@ -2,25 +2,17 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.Documentation.Configuration.Serialization; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - namespace Elastic.Documentation.Configuration.Versions; public static class VersionsConfigurationExtensions { public static VersionsConfiguration CreateVersionConfiguration(this ConfigurationFileProvider provider) { - var path = provider.VersionFile; - - var deserializer = new StaticDeserializerBuilder(new YamlStaticContext()) - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); + var versionFilePath = provider.VersionFile; - var dto = deserializer.Deserialize(path.OpenText()); + var versionsDto = ConfigurationFileProvider.Deserializer.Deserialize(versionFilePath.OpenText()); - var versions = dto.VersioningSystems.ToDictionary( + var versions = versionsDto.VersioningSystems.ToDictionary( kvp => ToVersioningSystemId(kvp.Key), kvp => new VersioningSystem { @@ -29,10 +21,11 @@ public static VersionsConfiguration CreateVersionConfiguration(this Configuratio Current = ToSemVersion(kvp.Value.Current) }); var config = new VersionsConfiguration { VersioningSystems = versions }; + return config; } - private static VersioningSystemId ToVersioningSystemId(string id) + internal static VersioningSystemId ToVersioningSystemId(string id) { if (!VersioningSystemIdExtensions.TryParse(id, out var versioningSystemId, true, true)) throw new InvalidOperationException($"Could not parse versioning system id {id}"); @@ -66,3 +59,4 @@ internal sealed record VersioningSystemDto public string Base { get; set; } = string.Empty; public string Current { get; set; } = string.Empty; } + diff --git a/src/Elastic.Documentation.LegacyDocs/PageLegacyUrlMapper.cs b/src/Elastic.Documentation.LegacyDocs/PageLegacyUrlMapper.cs index 7321bb042..2553a86ab 100644 --- a/src/Elastic.Documentation.LegacyDocs/PageLegacyUrlMapper.cs +++ b/src/Elastic.Documentation.LegacyDocs/PageLegacyUrlMapper.cs @@ -2,46 +2,47 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Versions; namespace Elastic.Documentation.LegacyDocs; public record PageLegacyUrlMapper : ILegacyUrlMapper { - private IReadOnlyDictionary> PreviousUrls { get; } private LegacyPageService LegacyPageService { get; } - public PageLegacyUrlMapper(LegacyPageService legacyPageService, IReadOnlyDictionary> previousUrls) + private string DefaultVersion { get; } + private LegacyUrlMappingConfiguration LegacyUrlMappings { get; } + + public PageLegacyUrlMapper(LegacyPageService legacyPageService, VersionsConfiguration versions, LegacyUrlMappingConfiguration legacyUrlMappings) { - PreviousUrls = previousUrls; LegacyPageService = legacyPageService; + DefaultVersion = $"{versions.VersioningSystems[VersioningSystemId.Stack].Base.Major}.{versions.VersioningSystems[VersioningSystemId.Stack].Base.Minor}"; + LegacyUrlMappings = legacyUrlMappings; } + public IReadOnlyCollection? MapLegacyUrl(IReadOnlyCollection? mappedPages) { - if (mappedPages is null) + if (mappedPages is null || mappedPages.Count == 0) return null; - if (mappedPages.Count == 0) - return [new LegacyPageMapping(mappedPages.FirstOrDefault() ?? string.Empty, string.Empty, false)]; - var mappedPage = mappedPages.First(); - var versions = PreviousUrls.FirstOrDefault(kv => + if (LegacyUrlMappings.Mappings.FirstOrDefault(x => mappedPage.Contains(x.BaseUrl, StringComparison.OrdinalIgnoreCase)) is not { } legacyMappingMatch) { - var (key, _) = kv; - return mappedPage.Contains(key, StringComparison.OrdinalIgnoreCase); - }); - - if (versions.Value is null) - return [new LegacyPageMapping(mappedPages.FirstOrDefault() ?? string.Empty, string.Empty, false)]; - return versions.Value - .Select(v => - { - var legacyPageMapping = new LegacyPageMapping(mappedPage, v, true); - var path = Uri.TryCreate(legacyPageMapping.ToString(), UriKind.Absolute, out var uri) ? uri : null; - var exists = LegacyPageService.PathExists(path?.AbsolutePath!); - return legacyPageMapping with { Exists = exists }; - } - ).ToArray(); + return [new LegacyPageMapping(LegacyUrlMappings.Mappings.First(x => x.Product.Id.Equals("elastic-stack", StringComparison.OrdinalIgnoreCase)).Product, mappedPages.FirstOrDefault() ?? string.Empty, DefaultVersion, false)]; + } + + var allVersions = new List(); + + allVersions.AddRange(legacyMappingMatch.LegacyVersions.Select(x => + { + var mapping = new LegacyPageMapping(legacyMappingMatch.Product, mappedPage, x, false); + var path = Uri.TryCreate(mapping.ToString(), UriKind.Absolute, out var uri) ? uri : null; + var exists = path is not null && LegacyPageService.PathExists(path.AbsolutePath); + return mapping with { Exists = exists }; + })); + + return allVersions; } } diff --git a/src/Elastic.Documentation.ServiceDefaults/AppDefaultsExtensions.cs b/src/Elastic.Documentation.ServiceDefaults/AppDefaultsExtensions.cs index 97cd05452..1786f3a1f 100644 --- a/src/Elastic.Documentation.ServiceDefaults/AppDefaultsExtensions.cs +++ b/src/Elastic.Documentation.ServiceDefaults/AppDefaultsExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.ServiceDefaults.Logging; using Microsoft.Extensions.DependencyInjection; @@ -30,7 +32,11 @@ public static TBuilder AddDocumentationServiceDefaults(this TBuilder b _ = services .AddConfigurationFileProvider(globalArgs.SkipPrivateRepositories, globalArgs.ConfigurationSource, (s, p) => { - _ = s.AddSingleton(p.CreateVersionConfiguration()); + var versionConfiguration = p.CreateVersionConfiguration(); + var products = p.CreateProducts(versionConfiguration); + _ = s.AddSingleton(p.CreateLegacyUrlMappings(products)); + _ = s.AddSingleton(products); + _ = s.AddSingleton(versionConfiguration); configure?.Invoke(s, p); }); _ = builder.Services.AddElasticDocumentationLogging(globalArgs.LogLevel); diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs index 563fcbc56..2aba29981 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs @@ -124,63 +124,63 @@ public record ProductApplicability [YamlMember(Alias = "curator")] public AppliesCollection? Curator { get; set; } - [YamlMember(Alias = "apm_agent_android")] + [YamlMember(Alias = "apm-agent-android")] public AppliesCollection? ApmAgentAndroid { get; set; } - [YamlMember(Alias = "apm_agent_dotnet")] + [YamlMember(Alias = "apm-agent-dotnet")] public AppliesCollection? ApmAgentDotnet { get; set; } - [YamlMember(Alias = "apm_agent_go")] + [YamlMember(Alias = "apm-agent-go")] public AppliesCollection? ApmAgentGo { get; set; } - [YamlMember(Alias = "apm_agent_ios")] + [YamlMember(Alias = "apm-agent-ios")] public AppliesCollection? ApmAgentIos { get; set; } - [YamlMember(Alias = "apm_agent_java")] + [YamlMember(Alias = "apm-agent-java")] public AppliesCollection? ApmAgentJava { get; set; } - [YamlMember(Alias = "apm_agent_node")] + [YamlMember(Alias = "apm-agent-node")] public AppliesCollection? ApmAgentNode { get; set; } - [YamlMember(Alias = "apm_agent_php")] + [YamlMember(Alias = "apm-agent-php")] public AppliesCollection? ApmAgentPhp { get; set; } - [YamlMember(Alias = "apm_agent_python")] + [YamlMember(Alias = "apm-agent-python")] public AppliesCollection? ApmAgentPython { get; set; } - [YamlMember(Alias = "apm_agent_ruby")] + [YamlMember(Alias = "apm-agent-ruby")] public AppliesCollection? ApmAgentRuby { get; set; } - [YamlMember(Alias = "apm_agent_rum")] - public AppliesCollection? ApmAgentRum { get; set; } + [YamlMember(Alias = "apm-agent-rum-js")] + public AppliesCollection? ApmAgentRumJs { get; set; } - [YamlMember(Alias = "edot_ios")] + [YamlMember(Alias = "edot-ios")] public AppliesCollection? EdotIos { get; set; } - [YamlMember(Alias = "edot_android")] + [YamlMember(Alias = "edot-android")] public AppliesCollection? EdotAndroid { get; set; } - [YamlMember(Alias = "edot_dotnet")] + [YamlMember(Alias = "edot-dotnet")] public AppliesCollection? EdotDotnet { get; set; } - [YamlMember(Alias = "edot_java")] + [YamlMember(Alias = "edot-java")] public AppliesCollection? EdotJava { get; set; } - [YamlMember(Alias = "edot_node")] + [YamlMember(Alias = "edot-node")] public AppliesCollection? EdotNode { get; set; } - [YamlMember(Alias = "edot_php")] + [YamlMember(Alias = "edot-php")] public AppliesCollection? EdotPhp { get; set; } - [YamlMember(Alias = "edot_python")] + [YamlMember(Alias = "edot-python")] public AppliesCollection? EdotPython { get; set; } - [YamlMember(Alias = "edot_cf_aws")] + [YamlMember(Alias = "edot-cf-aws")] public AppliesCollection? EdotCfAws { get; set; } - [YamlMember(Alias = "edot_cf_azure")] + [YamlMember(Alias = "edot-cf-azure")] public AppliesCollection? EdotCfAzure { get; set; } - [YamlMember(Alias = "edot_collector")] + [YamlMember(Alias = "edot-collector")] public AppliesCollection? EdotCollector { get; set; } } diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index a95815a17..7d0f81dd7 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -10,16 +10,14 @@ namespace Elastic.Documentation.AppliesTo; -public class ApplicableToYamlConverter : IYamlTypeConverter +public class ApplicableToYamlConverter(IReadOnlyCollection productKeys) : IYamlTypeConverter { - private static readonly string[] KnownKeys = + private readonly string[] _knownKeys = [ - "stack", "deployment", "serverless", "product", - "ece", "eck", "ess", "self", - "elasticsearch", "observability", "security", - "ecctl", "curator", - "apm_agent_android","apm_agent_dotnet", "apm_agent_go", "apm_agent_ios", "apm_agent_java", "apm_agent_node", "apm_agent_php", "apm_agent_python", "apm_agent_ruby", "apm_agent_rum", - "edot_ios", "edot_android", "edot_dotnet", "edot_java", "edot_node", "edot_php", "edot_python", "edot_cf_aws", "edot_cf_azure" + "stack", "deployment", "serverless", "product", // Applicability categories + "ece", "eck", "ess", "self", // Deployment options + "elasticsearch", "observability", "security", // Serverless flavors + .. productKeys ]; public bool Accepts(Type type) => type == typeof(ApplicableTo); @@ -45,11 +43,11 @@ public class ApplicableToYamlConverter : IYamlTypeConverter if (deserialized is not Dictionary { Count: > 0 } dictionary) return null; - var keys = dictionary.Keys.OfType().ToArray(); + var keys = dictionary.Keys.OfType().Select(x => x.Replace('_', '-')).ToArray(); var oldStyleKeys = keys.Where(k => k.StartsWith(':')).ToList(); if (oldStyleKeys.Count > 0) diagnostics.Add((Severity.Warning, $"Applies block does not use valid yaml keys: {string.Join(", ", oldStyleKeys)}")); - var unknownKeys = keys.Except(KnownKeys).Except(oldStyleKeys).ToList(); + var unknownKeys = keys.Except(_knownKeys).Except(oldStyleKeys).ToList(); if (unknownKeys.Count > 0) diagnostics.Add((Severity.Warning, $"Applies block does not support the following keys: {string.Join(", ", unknownKeys)}")); @@ -220,7 +218,7 @@ private static bool TryGetProductApplicability(Dictionary dicti { "apm_agent_php", a => productAvailability.ApmAgentPhp = a }, { "apm_agent_python", a => productAvailability.ApmAgentPython = a }, { "apm_agent_ruby", a => productAvailability.ApmAgentRuby = a }, - { "apm_agent_rum", a => productAvailability.ApmAgentRum = a }, + { "apm_agent_rum_js", a => productAvailability.ApmAgentRumJs = a }, { "edot_ios", a => productAvailability.EdotIos = a }, { "edot_android", a => productAvailability.EdotAndroid = a }, { "edot_dotnet", a => productAvailability.EdotDotnet = a }, diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index ed2c02d9f..97e7a1db8 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -6,7 +6,7 @@ using System.Text.Json; using Elastic.Documentation; using Elastic.Documentation.Configuration; -using Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Links; using Elastic.Documentation.Links.CrossLinks; using Elastic.Documentation.Serialization; diff --git a/src/Elastic.Markdown/Exporters/ConfigurationExporter.cs b/src/Elastic.Markdown/Exporters/ConfigurationExporter.cs index 93f6bb03d..aa63507f1 100644 --- a/src/Elastic.Markdown/Exporters/ConfigurationExporter.cs +++ b/src/Elastic.Markdown/Exporters/ConfigurationExporter.cs @@ -54,6 +54,10 @@ public ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancellati _logger.LogInformation("Exporting {Name} to {ConfigFolder}", versionsConfig.Name, configFolder.FullName); fs.File.Copy(versionsConfig.FullName, Path.Combine(configFolder.FullName, versionsConfig.Name), true); + var productsConfig = configurationFileProvider.ProductsFile; + _logger.LogInformation("Exporting {Name} to {ConfigFolder}", productsConfig.Name, configFolder.FullName); + fs.File.Copy(productsConfig.FullName, Path.Combine(configFolder.FullName, productsConfig.Name), true); + return default; } } diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index ea54b9520..1ed4b7207 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -6,7 +6,6 @@ using System.IO.Compression; using System.Text; using Elastic.Documentation.Configuration; -using Elastic.Documentation.Configuration.Builder; using Elastic.Markdown.Helpers; using Markdig.Syntax; @@ -114,11 +113,11 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s if (!string.IsNullOrEmpty(sourceFile.Url)) _ = metadata.AppendLine($"url: {context.BuildContext.CanonicalBaseUrl?.Scheme}://{context.BuildContext.CanonicalBaseUrl?.Host}{sourceFile.Url}"); - var configProducts = context.BuildContext.Configuration.Products.Select(p => + var configProducts = context.BuildContext.ProductsConfiguration.Products.Select(p => { - if (Products.AllById.TryGetValue(p, out var product)) + if (context.BuildContext.ProductsConfiguration.Products.TryGetValue(p.Value.Id, out var product)) return product; - throw new ArgumentException($"Invalid product id: {p}"); + throw new ArgumentException($"Invalid product id: {p.Value.Id}"); }); var frontMatterProducts = sourceFile.YamlFrontMatter?.Products ?? []; var allProducts = frontMatterProducts @@ -128,8 +127,8 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s if (allProducts.Count > 0) { _ = metadata.AppendLine("products:"); - foreach (var product in allProducts.Select(p => p.DisplayName).Order()) - _ = metadata.AppendLine($" - {product}"); + foreach (var item in allProducts.Select(p => p.DisplayName).Order()) + _ = metadata.AppendLine($" - {item}"); } _ = metadata.AppendLine("---"); diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index 6b99f731f..5c68c29ee 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -5,8 +5,8 @@ using System.IO.Abstractions; using System.Text.Json; using Elastic.Documentation; -using Elastic.Documentation.Configuration.Builder; -using Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Site.FileProviders; using Elastic.Documentation.Site.Navigation; using Elastic.Markdown.Extensions.DetectionRules; @@ -84,14 +84,13 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc var reportUrl = $"https://github.com/elastic/docs-content/issues/new?template=issue-report.yaml&link={reportLinkParameter}&labels=source:web"; var siteName = DocumentationSet.Tree.Index.Title ?? "Elastic Documentation"; - var legacyPages = LegacyUrlMapper.MapLegacyUrl(markdown.YamlFrontMatter?.MappedPages); - var configProducts = DocumentationSet.Configuration.Products.Select(p => + var configProducts = DocumentationSet.Context.ProductsConfiguration.Products.Select(p => { - if (Products.AllById.TryGetValue(p, out var product)) + if (DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(p.Value.Id, out var product)) return product; - throw new ArgumentException($"Invalid product id: {p}"); + throw new ArgumentException($"Invalid product id: {p.Value.Id}"); }); var frontMatterProducts = markdown.YamlFrontMatter?.Products ?? []; @@ -114,6 +113,9 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc fullNavigationRenderResult ); + var currentBaseVersion = legacyPages is { Count: > 0 } + ? $"{legacyPages.ElementAt(0).Product.VersioningSystem?.Base.Major}.{legacyPages.ElementAt(0).Product.VersioningSystem?.Base.Minor}+" + : $"{DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack].Base.Major}.{DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack].Base.Minor}+"; //TODO should we even distinctby var breadcrumbs = parents.Reverse().DistinctBy(p => p.Url).ToArray(); var breadcrumbsList = CreateStructuredBreadcrumbsData(markdown, breadcrumbs); @@ -147,10 +149,10 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc Features = DocumentationSet.Configuration.Features, StaticFileContentHashProvider = StaticFileContentHashProvider, ReportIssueUrl = reportUrl, - CurrentVersion = legacyPages?.Count > 0 ? legacyPages.ElementAt(0).Version : "9.0+", + CurrentVersion = currentBaseVersion, AllVersionsUrl = allVersionsUrl, - LegacyPages = legacyPages?.Skip(1).ToArray(), - VersionDropdownItems = VersionDrownDownItemViewModel.FromLegacyPageMappings(legacyPages?.Skip(1).ToArray()), + LegacyPages = legacyPages?.ToArray(), + VersionDropdownItems = VersionDropDownItemViewModel.FromLegacyPageMappings(legacyPages?.ToArray()), Products = allProducts, VersionsConfig = DocumentationSet.Context.VersionsConfiguration, StructuredBreadcrumbsJson = structuredBreadcrumbsJsonString diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 596dc6d19..694ca8c5b 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -383,7 +383,7 @@ private YamlFrontMatter ReadYamlFrontMatter(string raw) { try { - return YamlSerialization.Deserialize(raw); + return YamlSerialization.Deserialize(raw, _set.Context.ProductsConfiguration); } catch (InvalidProductException e) { diff --git a/src/Elastic.Markdown/MarkdownLayoutViewModel.cs b/src/Elastic.Markdown/MarkdownLayoutViewModel.cs index f1034ffaf..7b947637b 100644 --- a/src/Elastic.Markdown/MarkdownLayoutViewModel.cs +++ b/src/Elastic.Markdown/MarkdownLayoutViewModel.cs @@ -2,7 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Site; using Elastic.Documentation.Site.Navigation; diff --git a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs index 63e91b4ee..962c74549 100644 --- a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs +++ b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs @@ -124,7 +124,7 @@ private static void ProcessAppliesToDirective(AppliesToDirective appliesToDirect try { - var applicableTo = YamlSerialization.Deserialize(yaml); + var applicableTo = YamlSerialization.Deserialize(yaml, appliesToDirective.Build.ProductsConfiguration); appliesToDirective.AppliesTo = applicableTo; if (appliesToDirective.AppliesTo.Diagnostics is null) return; diff --git a/src/Elastic.Markdown/Myst/Components/ApplicabilityMappings.cs b/src/Elastic.Markdown/Myst/Components/ApplicabilityMappings.cs index bd40ec4d7..f529f8163 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicabilityMappings.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicabilityMappings.cs @@ -51,7 +51,7 @@ public record ApplicabilityDefinition(string Key, string DisplayName, Versioning public static readonly ApplicabilityDefinition ApmAgentPhp = new("APM Agent PHP", "Application Performance Monitoring Agent for PHP", VersioningSystemId.ApmAgentPhp); public static readonly ApplicabilityDefinition ApmAgentPython = new("APM Agent Python", "Application Performance Monitoring Agent for Python", VersioningSystemId.ApmAgentPython); public static readonly ApplicabilityDefinition ApmAgentRuby = new("APM Agent Ruby", "Application Performance Monitoring Agent for Ruby", VersioningSystemId.ApmAgentRuby); - public static readonly ApplicabilityDefinition ApmAgentRum = new("APM Agent RUM", "Application Performance Monitoring Agent for Real User Monitoring", VersioningSystemId.ApmAgentRum); + public static readonly ApplicabilityDefinition ApmAgentRumJs = new("APM Agent RUM", "Application Performance Monitoring Agent for Real User Monitoring", VersioningSystemId.ApmAgentRumJs); // Generic product public static readonly ApplicabilityDefinition Product = new("", "", VersioningSystemId.All); diff --git a/src/Elastic.Markdown/Myst/Components/ApplicableToViewModel.cs b/src/Elastic.Markdown/Myst/Components/ApplicableToViewModel.cs index bb7cc6093..483d6eee1 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicableToViewModel.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicableToViewModel.cs @@ -56,7 +56,7 @@ public class ApplicableToViewModel [p => p.ApmAgentPhp] = ApplicabilityMappings.ApmAgentPhp, [p => p.ApmAgentPython] = ApplicabilityMappings.ApmAgentPython, [p => p.ApmAgentRuby] = ApplicabilityMappings.ApmAgentRuby, - [p => p.ApmAgentRum] = ApplicabilityMappings.ApmAgentRum + [p => p.ApmAgentRumJs] = ApplicabilityMappings.ApmAgentRumJs }; diff --git a/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs b/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs index 86f06e36c..221d555aa 100644 --- a/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs @@ -2,8 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.Linq; using Elastic.Documentation.AppliesTo; +using Elastic.Documentation.Configuration.Products; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; @@ -61,17 +61,17 @@ public override void FinalizeAndValidate(ParserContext context) AppliesSwitchGroupKey = appliesSwitch?.GetGroupKey(); // Auto-generate sync key from applies_to definition if not provided - SyncKey = Prop("sync") ?? GenerateSyncKey(AppliesToDefinition); + SyncKey = Prop("sync") ?? GenerateSyncKey(AppliesToDefinition, Build.ProductsConfiguration); Selected = PropBool("selected"); } - public static string GenerateSyncKey(string appliesToDefinition) + public static string GenerateSyncKey(string appliesToDefinition, ProductsConfiguration productsConfiguration) { // Parse the YAML to get the ApplicableTo object, then use its hash // This ensures both simple syntax and YAML objects produce consistent sync keys try { - var applicableTo = YamlSerialization.Deserialize(appliesToDefinition); + var applicableTo = YamlSerialization.Deserialize(appliesToDefinition, productsConfiguration); if (applicableTo != null) { // Use the object's hash for a consistent, unique identifier diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index faace8aa3..0abf739a3 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -287,7 +287,7 @@ private static void WriteAppliesItem(HtmlRenderer renderer, AppliesItemBlock blo { try { - var applicableTo = YamlSerialization.Deserialize(yaml); + var applicableTo = YamlSerialization.Deserialize(yaml, block.Build.ProductsConfiguration); if (applicableTo.Diagnostics is null) return applicableTo; foreach (var (severity, message) in applicableTo.Diagnostics) @@ -368,7 +368,7 @@ private static void WriteSettingsBlock(HtmlRenderer renderer, SettingsBlock bloc try { var yaml = file.FileSystem.File.ReadAllText(file.FullName); - settings = YamlSerialization.Deserialize(yaml); + settings = YamlSerialization.Deserialize(yaml, block.Context.Build.ProductsConfiguration); } catch (YamlException e) { diff --git a/src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs b/src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs index f58ba115d..8692adf92 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs @@ -3,7 +3,8 @@ // See the LICENSE file in the project root for more information using Elastic.Documentation.AppliesTo; -using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.Products; +using Elastic.Documentation.Configuration.Versions; using YamlDotNet.Serialization; namespace Elastic.Markdown.Myst.FrontMatter; diff --git a/src/Elastic.Markdown/Myst/FrontMatter/Products.cs b/src/Elastic.Markdown/Myst/FrontMatter/Products.cs index 40335b5e4..cb2cf064b 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/Products.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/Products.cs @@ -2,8 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.Collections.Frozen; -using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Suggestions; using YamlDotNet.Core; using YamlDotNet.Core.Events; @@ -11,7 +10,7 @@ namespace Elastic.Markdown.Myst.FrontMatter; -public class ProductConverter : IYamlTypeConverter +public class ProductConverter(ProductsConfiguration products) : IYamlTypeConverter { public bool Accepts(Type type) => type == typeof(Product); @@ -20,7 +19,7 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria if (parser.Current is Scalar) { var value = parser.Consume().Value; - throw new InvalidProductException($"Invalid YAML format. Products must be specified as a mapping with an 'id' field. Found scalar value: '{value}'. Example format:\nproducts:\n - id: apm"); + throw new InvalidProductException($"Invalid YAML format. Products must be specified as a mapping with an 'id' field. Found scalar value: '{value}'. Example format:\nproducts:\n - id: apm", products); } _ = parser.Consume(); @@ -38,25 +37,19 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria _ = parser.Consume(); if (string.IsNullOrWhiteSpace(productId)) - throw new InvalidProductException("Product 'id' field is required. Example format:\nproducts:\n - id: apm"); + throw new InvalidProductException("Product 'id' field is required. Example format:\nproducts:\n - id: apm", products); - if (Products.AllById.TryGetValue(productId, out var product)) + if (products.Products.TryGetValue(productId.Replace('_', '-'), out var product)) return product; - throw new InvalidProductException(productId); + throw new InvalidProductException(productId, products); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => serializer.Invoke(value, type); } -public class InvalidProductException(string invalidValue) +public class InvalidProductException(string invalidValue, ProductsConfiguration products) : Exception( $"Invalid products frontmatter value: \"{invalidValue}\"." + - (!string.IsNullOrWhiteSpace(invalidValue) ? " " + new Suggestion(ProductExtensions.GetProductIds(), invalidValue).GetSuggestionQuestion() : "") + + (!string.IsNullOrWhiteSpace(invalidValue) ? " " + new Suggestion(products.Products.Select(p => p.Value.Id).ToHashSet(), invalidValue).GetSuggestionQuestion() : "") + "\nYou can find the full list at https://docs-v3-preview.elastic.dev/elastic/docs-builder/tree/main/syntax/frontmatter#products."); - -public static class ProductExtensions -{ - public static IReadOnlySet GetProductIds() => - Products.All.Select(p => p.Id).ToFrozenSet(); -} diff --git a/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs b/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs index ad2e13627..d65d9dd06 100644 --- a/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs +++ b/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs @@ -20,8 +20,8 @@ public class AppliesToRole : RoleLeaf, IApplicableToElement { public AppliesToRole(string role, string content, InlineProcessor parserContext) : base(role, content) { - AppliesTo = ParseApplicableTo(content, parserContext); BuildContext = parserContext.GetContext().Build; + AppliesTo = ParseApplicableTo(content, parserContext); } public ApplicableTo? AppliesTo { get; } @@ -32,7 +32,7 @@ public AppliesToRole(string role, string content, InlineProcessor parserContext) { try { - var applicableTo = YamlSerialization.Deserialize(yaml); + var applicableTo = YamlSerialization.Deserialize(yaml, BuildContext.ProductsConfiguration); if (applicableTo.Diagnostics is null) return applicableTo; foreach (var (severity, message) in applicableTo.Diagnostics) diff --git a/src/Elastic.Markdown/Myst/YamlSerialization.cs b/src/Elastic.Markdown/Myst/YamlSerialization.cs index 857439a3c..4bf08dad3 100644 --- a/src/Elastic.Markdown/Myst/YamlSerialization.cs +++ b/src/Elastic.Markdown/Myst/YamlSerialization.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using Elastic.Documentation.AppliesTo; +using Elastic.Documentation.Configuration.Products; using Elastic.Markdown.Myst.Directives.Settings; using Elastic.Markdown.Myst.FrontMatter; using YamlDotNet.Serialization; @@ -12,7 +13,7 @@ namespace Elastic.Markdown.Myst; public static class YamlSerialization { - public static T Deserialize(string yaml) + public static T Deserialize(string yaml, ProductsConfiguration products) { var input = new StringReader(yaml); @@ -20,22 +21,20 @@ public static T Deserialize(string yaml) .IgnoreUnmatchedProperties() .WithEnumNamingConvention(HyphenatedNamingConvention.Instance) .WithTypeConverter(new SemVersionConverter()) - .WithTypeConverter(new ProductConverter()) - .WithTypeConverter(new ApplicableToYamlConverter()) + .WithTypeConverter(new ProductConverter(products)) + .WithTypeConverter(new ApplicableToYamlConverter(products.Products.Keys)) .Build(); var frontMatter = deserializer.Deserialize(input); return frontMatter; - } } [YamlStaticContext] [YamlSerializable(typeof(YamlSettings))] [YamlSerializable(typeof(SettingsGrouping))] -[YamlSerializable(typeof(YamlSettings))] -[YamlSerializable(typeof(SettingsGrouping))] [YamlSerializable(typeof(Setting))] [YamlSerializable(typeof(AllowedValue))] [YamlSerializable(typeof(SettingMutability))] +[YamlSerializable(typeof(ApplicableTo))] public partial class DocsBuilderYamlStaticContext; diff --git a/src/Elastic.Markdown/Page/Index.cshtml b/src/Elastic.Markdown/Page/Index.cshtml index 58c6b47c6..9cb7fa702 100644 --- a/src/Elastic.Markdown/Page/Index.cshtml +++ b/src/Elastic.Markdown/Page/Index.cshtml @@ -40,7 +40,7 @@ CurrentVersion = Model.CurrentDocument.YamlFrontMatter?.Layout == MarkdownPageLayout.LandingPage ? Model.VersionsConfig.VersioningSystems[0].Current : Model.CurrentVersion, AllVersionsUrl = Model.AllVersionsUrl, VersionDropdownSerializedModel = JsonSerializer.Serialize(Model.VersionDropdownItems, - ViewModelSerializerContext.Default.VersionDrownDownItemViewModelArray), + ViewModelSerializerContext.Default.VersionDropDownItemViewModelArray), }; protected override Task ExecuteSectionAsync(string name) { diff --git a/src/Elastic.Markdown/Page/IndexViewModel.cs b/src/Elastic.Markdown/Page/IndexViewModel.cs index b6a9ba096..7c3ce4761 100644 --- a/src/Elastic.Markdown/Page/IndexViewModel.cs +++ b/src/Elastic.Markdown/Page/IndexViewModel.cs @@ -6,8 +6,9 @@ using Elastic.Documentation.AppliesTo; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; -using Elastic.Documentation.Legacy; using Elastic.Documentation.Site.FileProviders; using Elastic.Documentation.Site.Navigation; using Elastic.Markdown.IO; @@ -26,7 +27,6 @@ public class IndexViewModel public required string MarkdownHtml { get; init; } public required IReadOnlyCollection PageTocItems { get; init; } public required MarkdownFile CurrentDocument { get; init; } - public required INavigationItem CurrentNavigationItem { get; init; } public required INavigationItem? PreviousDocument { get; init; } public required INavigationItem? NextDocument { get; init; } @@ -39,7 +39,7 @@ public class IndexViewModel public required string? AllVersionsUrl { get; init; } public required LegacyPageMapping[]? LegacyPages { get; init; } - public required VersionDrownDownItemViewModel[]? VersionDropdownItems { get; init; } + public required VersionDropDownItemViewModel[]? VersionDropdownItems { get; init; } public required string? UrlPathPrefix { get; init; } public required string? GithubEditUrl { get; init; } public required string MarkdownUrl { get; init; } @@ -61,7 +61,7 @@ public class IndexViewModel public required string StructuredBreadcrumbsJson { get; init; } } -public class VersionDrownDownItemViewModel +public class VersionDropDownItemViewModel { [JsonPropertyName("name")] public required string Name { get; init; } @@ -73,45 +73,49 @@ public class VersionDrownDownItemViewModel public required bool IsDisabled { get; init; } [JsonPropertyName("children")] - public required VersionDrownDownItemViewModel[]? Children { get; init; } + public required VersionDropDownItemViewModel[]? Children { get; init; } // This logic currently only handles one level of children. Although the model supports multiple levels, it is not currently used. - public static VersionDrownDownItemViewModel[]? FromLegacyPageMappings(LegacyPageMapping[]? legacyPageMappings) + public static VersionDropDownItemViewModel[]? FromLegacyPageMappings(LegacyPageMapping[]? legacyPageMappings) { - if (legacyPageMappings is null) + if (legacyPageMappings is null || legacyPageMappings.Length == 0) return null; var groupedVersions = GroupByMajorVersion(legacyPageMappings); - return groupedVersions.Select(m => + + List versions = []; + foreach (var versionGroup in groupedVersions) { - // If there is more than one version, we need to create a dropdown - if (m.Value.Count != 1) + if (versionGroup.Value.Count != 1) { - return new VersionDrownDownItemViewModel + versions.Add(new VersionDropDownItemViewModel { - Name = m.Key, + Name = versionGroup.Key, Href = null, IsDisabled = false, - Children = m.Value.Select(v => new VersionDrownDownItemViewModel + Children = versionGroup.Value.Select(v => new VersionDropDownItemViewModel { Name = v, Href = legacyPageMappings.First(x => x.Version == v).ToString(), IsDisabled = !legacyPageMappings.First(x => x.Version == v).Exists, Children = null }).ToArray() - }; + }); } + else + { + var legacyPageMapping = legacyPageMappings.First(x => x.Version == versionGroup.Value.First()); - var legacyPageMapping = legacyPageMappings.First(x => x.Version == m.Value.First()); + versions.Add(new VersionDropDownItemViewModel + { + Name = legacyPageMapping.Version, + Href = legacyPageMapping.ToString(), + IsDisabled = !legacyPageMapping.Exists, + Children = null + }); + } + } - // If there is only one version, we don't need to create a dropdown - return new VersionDrownDownItemViewModel - { - Name = legacyPageMapping.Version, - Href = legacyPageMapping.ToString(), - IsDisabled = !legacyPageMapping.Exists, - Children = null - }; - }).ToArray(); + return versions.ToArray(); } // The legacy page mappings provide a list of versions. @@ -132,5 +136,5 @@ private static Dictionary> GroupByMajorVersion(LegacyPageMa }); } -[JsonSerializable(typeof(VersionDrownDownItemViewModel[]))] +[JsonSerializable(typeof(VersionDropDownItemViewModel[]))] public partial class ViewModelSerializerContext : JsonSerializerContext; diff --git a/src/services/Elastic.Documentation.Assembler/AssembleContext.cs b/src/services/Elastic.Documentation.Assembler/AssembleContext.cs index 95c1137dc..7ef37b5e2 100644 --- a/src/services/Elastic.Documentation.Assembler/AssembleContext.cs +++ b/src/services/Elastic.Documentation.Assembler/AssembleContext.cs @@ -5,6 +5,8 @@ using System.IO.Abstractions; using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; @@ -27,6 +29,9 @@ public class AssembleContext : IDocumentationConfigurationContext /// public DocumentationEndpoints Endpoints { get; } + public ProductsConfiguration ProductsConfiguration { get; } + public LegacyUrlMappingConfiguration LegacyUrlMappings { get; } + public IDirectoryInfo CheckoutDirectory { get; } public IDirectoryInfo OutputDirectory { get; } @@ -52,6 +57,8 @@ public AssembleContext( ConfigurationFileProvider = configurationContext.ConfigurationFileProvider; VersionsConfiguration = configurationContext.VersionsConfiguration; Endpoints = configurationContext.Endpoints; + ProductsConfiguration = configurationContext.ProductsConfiguration; + LegacyUrlMappings = configurationContext.LegacyUrlMappings; if (!Configuration.Environments.TryGetValue(environment, out var env)) throw new Exception($"Could not find environment {environment}"); diff --git a/src/services/Elastic.Documentation.Assembler/AssembleSources.cs b/src/services/Elastic.Documentation.Assembler/AssembleSources.cs index 09976aefa..c7ed54546 100644 --- a/src/services/Elastic.Documentation.Assembler/AssembleSources.cs +++ b/src/services/Elastic.Documentation.Assembler/AssembleSources.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Configuration.Navigation; using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Links.CrossLinks; @@ -27,7 +28,7 @@ public class AssembleSources public FrozenDictionary NavigationTocMappings { get; } - public FrozenDictionary> LegacyUrlMappings { get; } + public LegacyUrlMappingConfiguration LegacyUrlMappings { get; } public FrozenDictionary TocConfigurationMapping { get; } @@ -46,7 +47,6 @@ Cancel ctx { var linkIndexProvider = Aws3LinkIndexReader.CreateAnonymous(); var navigationTocMappings = GetTocMappings(context); - var legacyUrlMappings = GetLegacyUrlMappings(context); var uriResolver = new PublishEnvironmentUriResolver(navigationTocMappings, context.Environment); var crossLinkFetcher = new AssemblerCrossLinkFetcher(logFactory, context.Configuration, context.Environment, linkIndexProvider); @@ -59,7 +59,7 @@ Cancel ctx checkouts, configurationContext, navigationTocMappings, - legacyUrlMappings, + configurationContext.LegacyUrlMappings, uriResolver, crossLinkResolver, availableExporters @@ -75,7 +75,7 @@ private AssembleSources( Checkout[] checkouts, IConfigurationContext configurationContext, FrozenDictionary navigationTocMappings, - FrozenDictionary> legacyUrlMappings, + LegacyUrlMappingConfiguration legacyUrlMappings, PublishEnvironmentUriResolver uriResolver, ICrossLinkResolver crossLinkResolver, IReadOnlySet availableExporters @@ -130,44 +130,6 @@ IReadOnlySet availableExporters .ToFrozenDictionary(); } - private static FrozenDictionary> GetLegacyUrlMappings(AssembleContext context) - { - var dictionary = new Dictionary>(); - var reader = new YamlStreamReader(context.ConfigurationFileProvider.LegacyUrlMappingsFile, context.Collector); - foreach (var entry in reader.Read()) - { - switch (entry.Key) - { - case "mappings": - ReadHistoryMappings(dictionary, reader, entry); - break; - } - } - - return dictionary.OrderByDescending(x => x.Key.Length).ToFrozenDictionary(); - - static void ReadHistoryMappings(IDictionary> dictionary, YamlStreamReader reader, YamlToplevelKey entry) - { - if (entry.Entry.Value is not YamlMappingNode mappings) - { - reader.EmitWarning($"It wasn't possible to read the mappings"); - return; - } - - foreach (var mapping in mappings) - { - var mappingKey = $"{((YamlScalarNode)mapping.Key).Value}"; - var mappingValues = ((YamlSequenceNode)mapping.Value).Children.OfType().Where(x => x.Value is not null).Select(x => x.Value!) - .ToList(); - if (dictionary.TryGetValue(mappingKey, out _)) - reader.EmitWarning($"'{mappingKey}' is already mapped to '{mappingValues}'"); - else - dictionary[mappingKey] = mappingValues; - } - } - } - - public static FrozenDictionary GetTocMappings(AssembleContext context) { var dictionary = new Dictionary(); diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs index 81d548de3..6a18791f3 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs @@ -82,7 +82,7 @@ Cancel ctx var pathProvider = new GlobalNavigationPathProvider(navigationFile, assembleSources, assembleContext); var htmlWriter = new GlobalNavigationHtmlWriter(logFactory, navigation, collector); var legacyPageChecker = new LegacyPageService(logFactory); - var historyMapper = new PageLegacyUrlMapper(legacyPageChecker, assembleSources.LegacyUrlMappings); + var historyMapper = new PageLegacyUrlMapper(legacyPageChecker, assembleContext.VersionsConfiguration, assembleSources.LegacyUrlMappings); var builder = new AssemblerBuilder(logFactory, assembleContext, navigation, htmlWriter, pathProvider, historyMapper); diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs index df15b6bf6..e2ee2e24b 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs @@ -7,7 +7,7 @@ using Elastic.Documentation.Assembler.Links; using Elastic.Documentation.Assembler.Navigation; using Elastic.Documentation.Configuration.Assembler; -using Elastic.Documentation.Legacy; +using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Links; using Elastic.Documentation.Links.CrossLinks; using Elastic.Documentation.Serialization; diff --git a/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs b/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs index 136c2759d..32c714b7b 100644 --- a/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs +++ b/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs @@ -7,6 +7,8 @@ using Actions.Core.Extensions; using Actions.Core.Services; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.ServiceDefaults; @@ -72,11 +74,15 @@ public static TBuilder AddDocumentationToolingDefaults(this TBuilder b var endpoints = sp.GetRequiredService(); var configurationFileProvider = sp.GetRequiredService(); var versionsConfiguration = sp.GetRequiredService(); + var products = sp.GetRequiredService(); + var legacyUrlMappings = sp.GetRequiredService(); return new ConfigurationContext { ConfigurationFileProvider = configurationFileProvider, VersionsConfiguration = versionsConfiguration, - Endpoints = endpoints + Endpoints = endpoints, + ProductsConfiguration = products, + LegacyUrlMappings = legacyUrlMappings }; }); diff --git a/tests/Elastic.ApiExplorer.Tests/TestHelpers.cs b/tests/Elastic.ApiExplorer.Tests/TestHelpers.cs index e77b4f32d..7efab701c 100644 --- a/tests/Elastic.ApiExplorer.Tests/TestHelpers.cs +++ b/tests/Elastic.ApiExplorer.Tests/TestHelpers.cs @@ -2,9 +2,12 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections.Frozen; using System.IO.Abstractions; using Elastic.Documentation; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Microsoft.Extensions.Logging.Abstractions; @@ -12,7 +15,7 @@ namespace Elastic.ApiExplorer.Tests; public static class TestHelpers { - public static IConfigurationContext CreateConfigurationContext(IFileSystem fileSystem, VersionsConfiguration? versionsConfiguration = null) + public static IConfigurationContext CreateConfigurationContext(IFileSystem fileSystem, VersionsConfiguration? versionsConfiguration = null, ProductsConfiguration? productsConfiguration = null) { versionsConfiguration ??= new VersionsConfiguration { @@ -26,7 +29,21 @@ public static IConfigurationContext CreateConfigurationContext(IFileSystem fileS Base = new SemVersion(8, 0, 0) } } - } + }, + }; + productsConfiguration ??= new ProductsConfiguration + { + Products = new Dictionary() + { + { + "elasticsearch", new Product + { + Id = "elasticsearch", + DisplayName = "Elasticsearch", + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersioningSystemId.Stack) + } + } + }.ToFrozenDictionary() }; return new ConfigurationContext { @@ -35,7 +52,9 @@ public static IConfigurationContext CreateConfigurationContext(IFileSystem fileS Elasticsearch = ElasticsearchEndpoint.Default, }, ConfigurationFileProvider = new ConfigurationFileProvider(NullLoggerFactory.Instance, fileSystem), - VersionsConfiguration = versionsConfiguration + VersionsConfiguration = versionsConfiguration, + ProductsConfiguration = productsConfiguration, + LegacyUrlMappings = new LegacyUrlMappingConfiguration { Mappings = [] }, }; } } diff --git a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs index 70e458bfd..0cbd76fc2 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs @@ -194,8 +194,8 @@ public void NormalizesSyncKeyOrder() foreach (var (definition1, definition2) in testCases) { - var key1 = AppliesItemBlock.GenerateSyncKey(definition1); - var key2 = AppliesItemBlock.GenerateSyncKey(definition2); + var key1 = AppliesItemBlock.GenerateSyncKey(definition1, Block!.Build.ProductsConfiguration); + var key2 = AppliesItemBlock.GenerateSyncKey(definition2, Block!.Build.ProductsConfiguration); key1.Should().Be(key2, $"Sync keys should be the same for '{definition1}' and '{definition2}'"); } } @@ -214,8 +214,8 @@ public void GeneratesConsistentSyncKeysForYamlObjects() foreach (var (yamlObject, equivalentSyntax) in testCases) { - var key1 = AppliesItemBlock.GenerateSyncKey(yamlObject); - var key2 = AppliesItemBlock.GenerateSyncKey(equivalentSyntax); + var key1 = AppliesItemBlock.GenerateSyncKey(yamlObject, Block!.Build.ProductsConfiguration); + var key2 = AppliesItemBlock.GenerateSyncKey(equivalentSyntax, Block!.Build.ProductsConfiguration); key1.Should().Be(key2, $"Sync keys should be the same for YAML object '{yamlObject}' and equivalent syntax '{equivalentSyntax}'"); // Also verify the key has the expected format diff --git a/tests/Elastic.Markdown.Tests/TestHelpers.cs b/tests/Elastic.Markdown.Tests/TestHelpers.cs index 2ca161122..cadad408c 100644 --- a/tests/Elastic.Markdown.Tests/TestHelpers.cs +++ b/tests/Elastic.Markdown.Tests/TestHelpers.cs @@ -2,16 +2,19 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections.Frozen; using System.IO.Abstractions; using Elastic.Documentation; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; namespace Elastic.Markdown.Tests; public static class TestHelpers { - public static IConfigurationContext CreateConfigurationContext(IFileSystem fileSystem, VersionsConfiguration? versionsConfiguration = null) + public static IConfigurationContext CreateConfigurationContext(IFileSystem fileSystem, VersionsConfiguration? versionsConfiguration = null, ProductsConfiguration? productsConfiguration = null) { versionsConfiguration ??= new VersionsConfiguration { @@ -25,7 +28,37 @@ public static IConfigurationContext CreateConfigurationContext(IFileSystem fileS Base = new SemVersion(8, 0, 0) } } - } + }, + }; + productsConfiguration ??= new ProductsConfiguration + { + Products = new Dictionary + { + { + "elasticsearch", new Product + { + Id = "elasticsearch", + DisplayName = "Elasticsearch", + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersioningSystemId.Stack) + } + }, + { + "apm", new Product + { + Id = "apm", + DisplayName = "APM", + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersioningSystemId.Stack) + } + }, + { + "apm-agent", new Product + { + Id = "apm-agent", + DisplayName = "APM Agent", + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersioningSystemId.Stack) + } + } + }.ToFrozenDictionary() }; return new ConfigurationContext { @@ -34,7 +67,9 @@ public static IConfigurationContext CreateConfigurationContext(IFileSystem fileS Elasticsearch = ElasticsearchEndpoint.Default, }, ConfigurationFileProvider = new ConfigurationFileProvider(new TestLoggerFactory(TestContext.Current.TestOutputHelper), fileSystem), - VersionsConfiguration = versionsConfiguration + VersionsConfiguration = versionsConfiguration, + ProductsConfiguration = productsConfiguration, + LegacyUrlMappings = new LegacyUrlMappingConfiguration { Mappings = [] }, }; } } diff --git a/tests/authoring/Framework/MarkdownDocumentAssertions.fs b/tests/authoring/Framework/MarkdownDocumentAssertions.fs index 084d8847f..315bd119a 100644 --- a/tests/authoring/Framework/MarkdownDocumentAssertions.fs +++ b/tests/authoring/Framework/MarkdownDocumentAssertions.fs @@ -31,6 +31,7 @@ module MarkdownDocumentAssertions = let actual = actual.Value let result = actual.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") let matter = result.File.YamlFrontMatter + matter.AppliesTo.Diagnostics <- expectedAvailability.Diagnostics match matter with | NonNull m -> let apply = m.AppliesTo diff --git a/tests/authoring/Framework/Setup.fs b/tests/authoring/Framework/Setup.fs index 791286a9e..83ecd7a33 100644 --- a/tests/authoring/Framework/Setup.fs +++ b/tests/authoring/Framework/Setup.fs @@ -6,13 +6,16 @@ namespace authoring open System +open System.Collections.Frozen open System.Collections.Generic open System.IO open System.IO.Abstractions.TestingHelpers open System.Threading.Tasks open Elastic.Documentation open Elastic.Documentation.Configuration +open Elastic.Documentation.Configuration.LegacyUrlMappings open Elastic.Documentation.Configuration.Versions +open Elastic.Documentation.Configuration.Products open Elastic.Markdown open Elastic.Markdown.IO open JetBrains.Annotations @@ -260,11 +263,24 @@ type Setup = ) let versionConfig = VersionsConfiguration(VersioningSystems = versioningSystems) + let productDict = Dictionary() + productDict.Add("elasticsearch", Product(Id = "elasticsearch", + DisplayName = "Elasticsearch", + VersioningSystem = versionConfig.VersioningSystems[VersioningSystemId.ElasticsearchProject])) + productDict.Add("apm_agent_dotnet", Product(Id = "apm_agent_dotnet", + DisplayName = "APM Agent for .NET", + VersioningSystem = versionConfig.VersioningSystems[VersioningSystemId.ApmAgentDotnet])) + productDict.Add("ecctl", Product(Id = "ecctl", + DisplayName = "Elastic Cloud Control ECCTL", + VersioningSystem = versionConfig.VersioningSystems[VersioningSystemId.Ecctl])) + let configurationFileProvider = ConfigurationFileProvider(new TestLoggerFactory(), fileSystem) let configurationContext = ConfigurationContext( VersionsConfiguration = versionConfig, ConfigurationFileProvider = configurationFileProvider, - Endpoints=DocumentationEndpoints(Elasticsearch = ElasticsearchEndpoint.Default) + Endpoints=DocumentationEndpoints(Elasticsearch = ElasticsearchEndpoint.Default), + ProductsConfiguration = ProductsConfiguration(Products = productDict.ToFrozenDictionary()), + LegacyUrlMappings = LegacyUrlMappingConfiguration(Mappings = []) ) let context = BuildContext( collector, diff --git a/tests/authoring/Inline/AppliesToRole.fs b/tests/authoring/Inline/AppliesToRole.fs index 2450d5af7..a035cc072 100644 --- a/tests/authoring/Inline/AppliesToRole.fs +++ b/tests/authoring/Inline/AppliesToRole.fs @@ -141,19 +141,22 @@ This is an inline {applies_to}`stack: preview 9.0, ga 9.1` element. markdown |> convertsToHtml """

This is an inline - +This functionality may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."> Stack GA planned + + + + + diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs index 36862d418..b4f9b347b 100644 --- a/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs @@ -67,6 +67,7 @@ public AssemblerConfigurationTests() public void ReadsConfigurationFiles() { Context.ConfigurationFileProvider.VersionFile.Name.Should().Be("versions.yml"); + Context.ConfigurationFileProvider.ProductsFile.Name.Should().Be("products.yml"); Context.ConfigurationFileProvider.NavigationFile.Name.Should().Be("navigation.yml"); Context.ConfigurationFileProvider.AssemblerFile.Name.Should().Be("assembler.yml"); Context.ConfigurationFileProvider.LegacyUrlMappingsFile.Name.Should().Be("legacy-url-mappings.yml"); diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/TestHelpers.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/TestHelpers.cs index 4a44260ac..778c2b926 100644 --- a/tests/docs-assembler.Tests/src/docs-assembler.Tests/TestHelpers.cs +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/TestHelpers.cs @@ -2,9 +2,12 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections.Frozen; using System.IO.Abstractions; using Elastic.Documentation; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Microsoft.Extensions.Logging.Abstractions; @@ -15,7 +18,8 @@ public static class TestHelpers public static IConfigurationContext CreateConfigurationContext( IFileSystem fileSystem, VersionsConfiguration? versionsConfiguration = null, - ConfigurationFileProvider? configurationFileProvider = null + ConfigurationFileProvider? configurationFileProvider = null, + ProductsConfiguration? productsConfiguration = null ) { configurationFileProvider ??= new ConfigurationFileProvider(NullLoggerFactory.Instance, fileSystem); @@ -31,7 +35,21 @@ public static IConfigurationContext CreateConfigurationContext( Base = new SemVersion(8, 0, 0) } } - } + }, + }; + productsConfiguration ??= new ProductsConfiguration + { + Products = new Dictionary + { + { + "elasticsearch", new Product + { + Id = "elasticsearch", + DisplayName = "Elasticsearch", + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersioningSystemId.Stack) + } + } + }.ToFrozenDictionary() }; return new ConfigurationContext { @@ -40,7 +58,9 @@ public static IConfigurationContext CreateConfigurationContext( Elasticsearch = ElasticsearchEndpoint.Default, }, ConfigurationFileProvider = configurationFileProvider, - VersionsConfiguration = versionsConfiguration + VersionsConfiguration = versionsConfiguration, + ProductsConfiguration = productsConfiguration, + LegacyUrlMappings = new LegacyUrlMappingConfiguration { Mappings = [] }, }; } }