Skip to content

Commit b6abc11

Browse files
Merge branch 'main' into charlotte-applies-to
2 parents baf3a86 + c245d33 commit b6abc11

38 files changed

+1140
-361
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
* @elastic/docs-engineering
1+
* @elastic/docs-engineering
2+
/docs/ @elastic/docs

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@
7070
</PackageVersion>
7171
<PackageVersion Include="xunit.v3" Version="2.0.2" />
7272
</ItemGroup>
73-
</Project>
73+
</Project>

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ Options:
2525
--force <bool?> Force a full rebuild of the destination folder (Default: null)
2626

2727
Commands:
28-
generate Converts a source markdown folder or file to an output folder
29-
serve Continuously serve a documentation folder at http://localhost:3000.
28+
generate Converts a source markdown folder or file to an output folder
29+
serve Continuously serve a documentation folder at http://localhost:3000.
30+
diff validate Validates redirect rules have been applied to the current branch.
3031
File systems changes will be reflected without having to restart the server.
3132
```
3233

@@ -118,6 +119,16 @@ https://github.com/elastic/{your-repository}/settings/pages
118119
119120
---
120121
122+
## Validating redirection rules
123+
124+
If documentation is moved, renamed or deleted, `docs-builder` can verify if changes in the working branch in relation to the default branch are reflected in the repository's `redirects.yml`. Verification in the local machine is currently supported.
125+
126+
`docs-builder diff validate <path>`
127+
128+
`<path>` is an optional parameter to customize the documentation folder path. It defaults to `docs`.
129+
130+
---
131+
121132
## Run without docker
122133

123134
You can use the .NET CLI to publish a self-contained `docs-builder` native code
@@ -140,7 +151,7 @@ existing surveyed tools
140151

141152
# Local Development
142153

143-
## Preqrequisites
154+
## Prerequisites
144155

145156
- [.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0)
146157
- [Node.js 22.13.1 (LTS)](https://nodejs.org/en/blog/release/v22.13.1)

docs-builder.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.ApiExplorer.Tests",
107107
EndProject
108108
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Site", "src\Elastic.Documentation.Site\Elastic.Documentation.Site.csproj", "{89B83007-71E6-4B57-BA78-2544BFA476DB}"
109109
EndProject
110+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.LegacyDocs", "src\Elastic.Documentation.LegacyDocs\Elastic.Documentation.LegacyDocs.csproj", "{111E7029-BB29-4039-9B45-04776798A8DD}"
111+
EndProject
112+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.LegacyDocs.Tests", "tests\Elastic.Documentation.LegacyDocs.Tests\Elastic.Documentation.LegacyDocs.Tests.csproj", "{164F55EC-9412-4CD4-81AD-3598B57632A6}"
113+
EndProject
110114
Global
111115
GlobalSection(SolutionConfigurationPlatforms) = preSolution
112116
Debug|Any CPU = Debug|Any CPU
@@ -184,6 +188,14 @@ Global
184188
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
185189
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
186190
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.Build.0 = Release|Any CPU
191+
{111E7029-BB29-4039-9B45-04776798A8DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
192+
{111E7029-BB29-4039-9B45-04776798A8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
193+
{111E7029-BB29-4039-9B45-04776798A8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
194+
{111E7029-BB29-4039-9B45-04776798A8DD}.Release|Any CPU.Build.0 = Release|Any CPU
195+
{164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
196+
{164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
197+
{164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
198+
{164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|Any CPU.Build.0 = Release|Any CPU
187199
EndGlobalSection
188200
GlobalSection(NestedProjects) = preSolution
189201
{4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
@@ -212,5 +224,7 @@ Global
212224
{C883AC18-7C6A-482E-A9D7-C44DF8633425} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
213225
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
214226
{89B83007-71E6-4B57-BA78-2544BFA476DB} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
227+
{111E7029-BB29-4039-9B45-04776798A8DD} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
228+
{164F55EC-9412-4CD4-81AD-3598B57632A6} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
215229
EndGlobalSection
216230
EndGlobal

docs/contribute/index.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,29 @@ To contribute to earlier versions of the Elastic Stack, you must work with our [
2424
* For **simple bugfixes and enhancements** --> [contribute on the web](on-the-web.md)
2525
* For **complex or multi-page updates** --> [Contribute locally](locally.md)
2626

27+
Starting with Elastic Stack version 9.0, ECE 4.0, and ECK 3.0, a new set of docs is no longer published for every minor release. Instead, each page stays valid over time and incorporates version-specific changes directly within the content using a [cumulative approach](#cumulative-docs).
28+
29+
#### Write cumulative documentation [#cumulative-docs]
30+
31+
Cumulative documentation means that one page can cover multiple product versions, deployment types, and release stages. Instead of creating separate pages for each release, we update the same page with version-specific details.
32+
33+
This helps readers understand which parts of the content apply to their own ecosystem and product versions, without needing to switch between different versions of a page.
34+
35+
Following this approach, information for supported versions must not be removed unless it was never accurate. Instead, refactor the content to improve clarity or to include details for other products, deployment types, or versions.
36+
37+
In order to achieve this, the markdown source files integrate a **tagging system** meant to identify:
38+
39+
* Which Elastic products and deployment models the content applies to.
40+
* When a feature goes into a new state as compared to the established base version.
41+
42+
This [tagging system](#applies-to) is mandatory for all of the public-facing documentation.
43+
44+
##### The `applies_to` tag [#applies-to]
45+
46+
Use the [`applies_to`](../syntax/applies.md) tag to indicate which versions, deployment types, or release stages each part of the content is relevant to.
47+
48+
You must always use the `applies_to` tag at the [page](../syntax/applies.md#page-annotations) level. Optionally, you can also use it at the [section](../syntax/applies.md#sections) or [inline](../syntax/applies.md#inline-applies-to) level if certain parts of the content apply only to specific versions, deployment types, or release stages.
49+
2750
## Report a bug
2851

2952
* It's a **documentation** problem --> [Open a docs issue](https://github.com/elastic/docs-content/issues/new?template=internal-request.yaml) *or* [Fix it myself](locally.md)
@@ -36,4 +59,4 @@ To contribute to earlier versions of the Elastic Stack, you must work with our [
3659

3760
## Work on docs-builder
3861

39-
That sounds great! See [development](../development/index.md) to learn how to contribute to our documentation build system.
62+
That sounds great! See [development](../development/index.md) to learn how to contribute to our documentation build system.
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Collections;
6+
using System.Security.Cryptography;
7+
using System.Text;
8+
9+
namespace Elastic.Documentation.LegacyDocs;
10+
11+
internal sealed class BloomFilter
12+
{
13+
/// <summary>
14+
/// The bit array for the filter.
15+
/// </summary>
16+
private readonly BitArray _bitArray;
17+
18+
/// <summary>
19+
/// The size of the bit array.
20+
/// </summary>
21+
private int Size => _bitArray.Length;
22+
23+
/// <summary>
24+
/// The number of hash functions used.
25+
/// </summary>
26+
private int HashCount { get; }
27+
28+
/// <summary>
29+
/// Private constructor to be used by factory methods.
30+
/// </summary>
31+
private BloomFilter(int size, int hashCount)
32+
{
33+
if (size <= 0)
34+
throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
35+
if (hashCount <= 0)
36+
throw new ArgumentOutOfRangeException(nameof(hashCount), "Hash count must be greater than zero.");
37+
38+
_bitArray = new BitArray(size);
39+
HashCount = hashCount;
40+
}
41+
42+
/// <summary>
43+
/// Initializes a new BloomFilter with optimal parameters based on expected items and false positive probability.
44+
/// </summary>
45+
/// <param name="expectedItems">The expected number of items to be stored.</param>
46+
/// <param name="falsePositiveProbability">The desired false positive probability (e.g., 0.01 for 1%).</param>
47+
private BloomFilter(int expectedItems, double falsePositiveProbability)
48+
{
49+
if (expectedItems <= 0)
50+
throw new ArgumentOutOfRangeException(nameof(expectedItems), "Expected items must be greater than zero.");
51+
if (falsePositiveProbability is <= 0.0 or >= 1.0)
52+
throw new ArgumentOutOfRangeException(nameof(falsePositiveProbability), "False positive probability must be between 0 and 1.");
53+
54+
var size = GetOptimalSize(expectedItems, falsePositiveProbability);
55+
var hashCount = GetOptimalHashCount(size, expectedItems);
56+
57+
_bitArray = new BitArray(size);
58+
HashCount = hashCount;
59+
}
60+
61+
/// <summary>
62+
/// Adds an item to the Bloom Filter.
63+
/// </summary>
64+
/// <param name="item">The item to add. The string will be UTF-8 encoded for hashing.</param>
65+
private void Add(string item)
66+
{
67+
var itemBytes = Encoding.UTF8.GetBytes(item);
68+
for (var i = 0; i < HashCount; i++)
69+
{
70+
var hash = GetHash(itemBytes, i);
71+
_bitArray[hash] = true;
72+
}
73+
}
74+
75+
/// <summary>
76+
/// Checks if an item is possibly in the set.
77+
/// </summary>
78+
/// <param name="item">The item to check.</param>
79+
/// <returns>False if the item is definitely not in the set, True if it might be.</returns>
80+
public bool Check(string item)
81+
{
82+
var itemBytes = Encoding.UTF8.GetBytes(item);
83+
for (var i = 0; i < HashCount; i++)
84+
{
85+
var hash = GetHash(itemBytes, i);
86+
if (!_bitArray[hash])
87+
return false;
88+
}
89+
return true;
90+
}
91+
92+
/// <summary>
93+
/// Hashes the input data using SHA256 with a given seed.
94+
/// </summary>
95+
private int GetHash(byte[] data, int seed)
96+
{
97+
var seedBytes = BitConverter.GetBytes(seed);
98+
var combinedBytes = new byte[data.Length + seedBytes.Length];
99+
Buffer.BlockCopy(data, 0, combinedBytes, 0, data.Length);
100+
Buffer.BlockCopy(seedBytes, 0, combinedBytes, data.Length, seedBytes.Length);
101+
var hashBytes = SHA256.HashData(combinedBytes);
102+
var hashInt = BitConverter.ToInt32(hashBytes, 0);
103+
return Math.Abs(hashInt % _bitArray.Length);
104+
}
105+
106+
/// <summary>
107+
/// Creates a new BloomFilter from a collection of items.
108+
/// </summary>
109+
/// <param name="items">The collection of string items to add.</param>
110+
/// <param name="falsePositiveProbability">The desired false positive probability.</param>
111+
/// <returns>A new BloomFilter instance populated with the items.</returns>
112+
public static BloomFilter FromCollection(IEnumerable<string> items, double falsePositiveProbability)
113+
{
114+
var itemList = new List<string>(items);
115+
var filter = new BloomFilter(itemList.Count, falsePositiveProbability);
116+
foreach (var item in itemList)
117+
filter.Add(item);
118+
119+
return filter;
120+
}
121+
122+
// --- Persistence Methods ---
123+
124+
/// <summary>
125+
/// Saves the Bloom Filter's state to a binary file.
126+
/// The format is: [4-byte Size int][4-byte HashCount int][bit array bytes]
127+
/// </summary>
128+
/// <param name="filePath">The path to the file.</param>
129+
public void Save(string filePath)
130+
{
131+
using var stream = File.Open(filePath, FileMode.Create);
132+
using var writer = new BinaryWriter(stream);
133+
// 1. Write the Size and HashCount as integers
134+
writer.Write(Size);
135+
writer.Write(HashCount);
136+
137+
// 2. Write the bit array
138+
var bitArrayBytes = new byte[(Size + 7) / 8];
139+
_bitArray.CopyTo(bitArrayBytes, 0);
140+
writer.Write(bitArrayBytes);
141+
}
142+
143+
/// <summary>
144+
/// Loads a Bloom Filter from a stream.
145+
/// The stream is expected to contain data in the same format as Save() produces.
146+
/// </summary>
147+
/// <param name="stream">The stream containing the filter data.</param>
148+
/// <returns>A new BloomFilter instance.</returns>
149+
public static BloomFilter Load(Stream stream)
150+
{
151+
// Use a BinaryReader, but leave the stream open as it's managed externally.
152+
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
153+
154+
// 1. Read metadata (Size and HashCount)
155+
var size = reader.ReadInt32();
156+
var hashCount = reader.ReadInt32();
157+
158+
// 2. Create a new filter with the loaded parameters
159+
var filter = new BloomFilter(size, hashCount);
160+
161+
// 3. Read the bit array data
162+
var byteCount = (size + 7) / 8;
163+
var bitArrayBytes = reader.ReadBytes(byteCount);
164+
165+
// Re-initialize the internal BitArray with the loaded data
166+
for (var i = 0; i < size; i++)
167+
{
168+
if ((bitArrayBytes[i / 8] & (1 << (i % 8))) != 0)
169+
filter._bitArray[i] = true;
170+
}
171+
172+
return filter;
173+
}
174+
175+
176+
// --- Optimal Parameter Calculation ---
177+
178+
/// <summary>
179+
/// Calculates the optimal size of the bit array (m).
180+
/// Formula: m = - (n * log(p)) / (log(2)^2)
181+
/// </summary>
182+
private static int GetOptimalSize(int n, double p) => (int)Math.Ceiling(-1 * (n * Math.Log(p)) / Math.Pow(Math.Log(2), 2));
183+
184+
/// <summary>
185+
/// Calculates the optimal number of hash functions (k).
186+
/// Formula: k = (m/n) * log(2)
187+
/// </summary>
188+
private static int GetOptimalHashCount(int m, int n) => (int)Math.Ceiling((double)m / n * Math.Log(2));
189+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<OutputType>Library</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>Elastic.Documentation.LegacyDocs</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="System.IO.Abstractions" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<EmbeddedResource Include="legacy-pages.bloom.bin" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Elastic.Documentation.Configuration;
6+
7+
namespace Elastic.Documentation.LegacyDocs;
8+
9+
public class LegacyPageChecker
10+
{
11+
private BloomFilter? _bloomFilter;
12+
private const string RootNamespace = "Elastic.Documentation.LegacyDocs";
13+
private const string FileName = "legacy-pages.bloom.bin";
14+
private const string ResourceName = $"{RootNamespace}.{FileName}";
15+
private readonly string _bloomFilterBinaryPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", RootNamespace, FileName);
16+
17+
18+
public bool PathExists(string path)
19+
{
20+
_bloomFilter ??= LoadBloomFilter();
21+
return _bloomFilter.Check(path);
22+
}
23+
24+
private static BloomFilter LoadBloomFilter()
25+
{
26+
var assembly = typeof(LegacyPageChecker).Assembly;
27+
using var stream = assembly.GetManifestResourceStream(ResourceName) ?? throw new FileNotFoundException(
28+
$"Embedded resource '{ResourceName}' not found in assembly '{assembly.FullName}'. " +
29+
"Ensure the Build Action for 'legacy-pages.bloom.bin' is 'Embedded Resource' and the path/name is correct.");
30+
return BloomFilter.Load(stream);
31+
}
32+
33+
public void GenerateBloomFilterBinary(IPagesProvider pagesProvider)
34+
{
35+
var pages = pagesProvider.GetPages();
36+
var enumerable = pages as string[] ?? pages.ToArray();
37+
var paths = enumerable.ToHashSet();
38+
var bloomFilter = BloomFilter.FromCollection(enumerable, 0.001);
39+
Console.WriteLine(paths.Count);
40+
bloomFilter.Save(_bloomFilterBinaryPath);
41+
}
42+
}

0 commit comments

Comments
 (0)