Skip to content

Commit 1b7e496

Browse files
Merge branch 'main' into charlotte-query-api-keys-examples
2 parents b5bc5b5 + 578d922 commit 1b7e496

File tree

21 files changed

+2087
-1610
lines changed

21 files changed

+2087
-1610
lines changed

.github/validate-pr/index.js

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import * as core from '@actions/core'
2727
import { copyFile } from 'fs/promises'
2828
import * as github from '@actions/github'
2929
import specification from '../../output/schema/schema.json' with { type: 'json' }
30+
import baselineValidation from '../../../clients-flight-recorder/recordings/types-validation/types-validation.json' with { type: 'json' }
3031
import { run as getReport } from '../../../clients-flight-recorder/scripts/types-validator/index.js'
3132
import {
3233
getNamespace,
@@ -81,7 +82,7 @@ async function run() {
8182
const specFiles = files.filter(
8283
(file) => file.includes('specification') && !file.includes('compiler/test')
8384
)
84-
const table = []
85+
const reports = new Map()
8586

8687
cd(tsValidationPath)
8788

@@ -109,37 +110,47 @@ async function run() {
109110
ci: false,
110111
verbose: false
111112
})
112-
table.push(buildTableLine(api, report))
113+
const [namespace, _method] = api.split('.')
114+
// Asked to validate a specific API, so we only store that one
115+
reports.set(api, report.get(namespace)[0])
113116
}
114117
} else {
118+
const api = getApi(file)
115119
const report = await getReport({
116-
api: getApi(file),
120+
api,
117121
'generate-report': false,
118122
request: true,
119123
response: true,
120124
ci: false,
121125
verbose: false
122126
})
123-
table.push(buildTableLine(getApi(file), report))
127+
128+
const [namespace, _method] = api.split('.')
129+
// Asked to validate a specific API, so we only store that one
130+
reports.set(api, report.get(namespace)[0])
124131
}
125132
}
126133

127134
cd(path.join(__dirname, '..', '..'))
128135

129-
table.sort((a, b) => {
130-
if (a < b) return -1
131-
if (a > b) return 1
132-
return 0
133-
})
136+
// Compare current reports with baseline and find changes
137+
const changedApis = []
138+
for (const [apiName, report] of reports) {
139+
const baselineReport = findBaselineReport(apiName, baselineValidation)
140+
if (baselineReport && hasChanges(baselineReport, report, apiName)) {
141+
changedApis.push({ api: apiName, baseline: baselineReport, current: report })
142+
}
143+
}
144+
changedApis.sort((a, b) => a.api.localeCompare(b.api))
134145

135-
if (table.length > 0) {
136-
let comment = `Following you can find the validation results for the API${table.length === 1 ? '' : 's'} you have changed.\n\n`
146+
if (changedApis.length > 0) {
147+
let comment = `Following you can find the validation changes for the API${changedApis.length === 1 ? '' : 's'} you have modified.\n\n`
137148
comment += '| API | Status | Request | Response |\n'
138149
comment += '| --- | --- | --- | --- |\n'
139-
for (const line of [...new Set(table)]) {
140-
comment += line
150+
for (const change of changedApis) {
151+
comment += buildDiffTableLine(change)
141152
}
142-
comment += `\nYou can validate ${table.length === 1 ? 'this' : 'these'} API${table.length === 1 ? '' : 's'} yourself by using the ${tick}make validate${tick} target.\n`
153+
comment += `\nYou can validate ${changedApis.length === 1 ? 'this' : 'these'} API${changedApis.length === 1 ? '' : 's'} yourself by using the ${tick}make validate${tick} target.\n`
143154

144155
core.setOutput('has_results', 'true')
145156
core.setOutput('comment_body', comment)
@@ -154,39 +165,70 @@ function getApi (file) {
154165
return file.split('/').slice(1, 3).filter(s => !privateNames.includes(s)).filter(Boolean).join('.')
155166
}
156167

157-
function buildTableLine (api, report) {
158-
const apiReport = report.get(getNamespace(api)).find(r => r.api === getName(api))
159-
return `| ${tick}${api}${tick} | ${generateStatus(apiReport)} | ${generateRequest(apiReport)} | ${generateResponse(apiReport)} |\n`
168+
169+
function findBaselineReport(apiName, baselineValidation) {
170+
const [namespace, method] = apiName.split('.')
171+
172+
if (!baselineValidation.namespaces[namespace]) {
173+
return null
174+
}
175+
176+
return baselineValidation.namespaces[namespace].apis.find(api => api.api === method)
160177
}
161178

162-
function generateStatus (report) {
163-
if (!report.diagnostics.hasRequestType || !report.diagnostics.hasResponseType) {
179+
function hasChanges(baselineReport, report) {
180+
if (!report) return false
181+
182+
return baselineReport.status !== report.status ||
183+
baselineReport.passingRequest !== report.passingRequest ||
184+
baselineReport.passingResponse !== report.passingResponse
185+
}
186+
187+
function buildDiffTableLine(change) {
188+
const { api, baseline, current } = change
189+
190+
const status = generateStatus(current.status)
191+
const request = generateRequest(current)
192+
const response = generateResponse(current)
193+
194+
const baselineStatus = generateStatus(baseline.status)
195+
const baselineRequest = generateRequest(baseline)
196+
const baselineResponse = generateResponse(baseline)
197+
198+
const statusDiff = status !== baselineStatus ? `${baselineStatus}${status}` : status
199+
const requestDiff = request !== baselineRequest ? `${baselineRequest}${request}` : request
200+
const responseDiff = response !== baselineResponse ? `${baselineResponse}${response}` : response
201+
202+
return `| ${tick}${api}${tick} | ${statusDiff} | ${requestDiff} | ${responseDiff} |\n`
203+
}
204+
205+
206+
function generateStatus (status) {
207+
if (status === 'missing_types' || status === 'missing_request_type' || status === 'missing_response_type') {
164208
return ':orange_circle:'
165209
}
166-
if (report.totalRequest <= 0 || report.totalResponse <= 0) {
210+
if (status === 'missing_test') {
167211
return ':white_circle:'
168212
}
169-
if (report.diagnostics.request.length === 0 && report.diagnostics.response.length === 0) {
213+
if (status === 'passing') {
170214
return ':green_circle:'
171215
}
172216
return ':red_circle:'
173217
}
174218

175219
function generateRequest (r) {
176-
if (r.totalRequest === -1) return 'Missing recording'
177-
if (!r.diagnostics.hasRequestType) return 'Missing type'
178-
if (r.totalRequest === 0) return 'Missing test'
220+
if (r.status === 'missing_test') return 'Missing test'
221+
if (r.status === 'missing_types' || r.status == 'missing_request_type') return 'Missing type'
179222
return `${r.passingRequest}/${r.totalRequest}`
180223
}
181224

182225
function generateResponse (r) {
183-
if (r.totalResponse === -1) return 'Missing recording'
184-
if (!r.diagnostics.hasResponseType) return 'Missing type'
185-
if (r.totalResponse === 0) return 'Missing test'
226+
if (r.status === 'missing_test') return 'Missing test'
227+
if (r.status === 'missing_types' || r.status == 'missing_response_type') return 'Missing type'
186228
return `${r.passingResponse}/${r.totalResponse}`
187229
}
188230

189231
run().catch((err) => {
190232
core.error(err)
191233
process.exit(1)
192-
})
234+
})

compiler-rs/clients_schema/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub trait Documented {
5858
pub trait ExternalDocument {
5959
fn ext_doc_id(&self) -> Option<&str>;
6060
fn ext_doc_url(&self) -> Option<&str>;
61+
fn ext_previous_version_doc_url(&self) -> Option<&str>;
6162
}
6263

6364
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -322,6 +323,9 @@ pub struct Property {
322323
#[serde(skip_serializing_if = "Option::is_none")]
323324
pub ext_doc_url: Option<String>,
324325

326+
#[serde(skip_serializing_if = "Option::is_none")]
327+
pub ext_previous_version_doc_url: Option<String>,
328+
325329
#[serde(skip_serializing_if = "Option::is_none")]
326330
pub ext_doc_id: Option<String>,
327331

@@ -371,6 +375,10 @@ impl ExternalDocument for Property {
371375
self.ext_doc_url.as_deref()
372376
}
373377

378+
fn ext_previous_version_doc_url(&self) -> Option<&str> {
379+
self.ext_previous_version_doc_url.as_deref()
380+
}
381+
374382
fn ext_doc_id(&self) -> Option<&str> {
375383
self.ext_doc_id.as_deref()
376384
}
@@ -528,6 +536,9 @@ pub struct BaseType {
528536
#[serde(skip_serializing_if = "Option::is_none")]
529537
pub ext_doc_url: Option<String>,
530538

539+
#[serde(skip_serializing_if = "Option::is_none")]
540+
pub ext_previous_version_doc_url: Option<String>,
541+
531542
#[serde(skip_serializing_if = "Option::is_none")]
532543
pub ext_doc_id: Option<String>,
533544

@@ -568,6 +579,7 @@ impl BaseType {
568579
spec_location: None,
569580
ext_doc_id: None,
570581
ext_doc_url: None,
582+
ext_previous_version_doc_url: None,
571583
}
572584
}
573585
}
@@ -591,6 +603,10 @@ impl ExternalDocument for BaseType {
591603
self.ext_doc_url.as_deref()
592604
}
593605

606+
fn ext_previous_version_doc_url(&self) -> Option<&str> {
607+
self.ext_previous_version_doc_url.as_deref()
608+
}
609+
594610
fn ext_doc_id(&self) -> Option<&str> {
595611
self.ext_doc_id.as_deref()
596612
}
@@ -619,6 +635,10 @@ impl<T: WithBaseType> ExternalDocument for T {
619635
self.base().doc_url()
620636
}
621637

638+
fn ext_previous_version_doc_url(&self) -> Option<&str> {
639+
self.base().ext_previous_version_doc_url()
640+
}
641+
622642
fn ext_doc_id(&self) -> Option<&str> {
623643
self.base().doc_id()
624644
}
@@ -895,6 +915,9 @@ pub struct Endpoint {
895915
#[serde(skip_serializing_if = "Option::is_none")]
896916
pub ext_doc_url: Option<String>,
897917

918+
#[serde(skip_serializing_if = "Option::is_none")]
919+
pub ext_previous_version_doc_url: Option<String>,
920+
898921
#[serde(skip_serializing_if = "Option::is_none")]
899922
pub deprecation: Option<Deprecation>,
900923

@@ -945,6 +968,10 @@ impl ExternalDocument for Endpoint {
945968
self.ext_doc_url.as_deref()
946969
}
947970

971+
fn ext_previous_version_doc_url(&self) -> Option<&str> {
972+
self.ext_previous_version_doc_url.as_deref()
973+
}
974+
948975
fn ext_doc_id(&self) -> Option<&str> {
949976
self.ext_doc_id.as_deref()
950977
}

compiler-rs/clients_schema_to_openapi/src/schemas.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,14 @@ impl<'a> TypesAndComponents<'a> {
215215
.as_ref()
216216
.and_then(|i| i.version.as_deref())
217217
.unwrap_or("current");
218+
let mut extensions: IndexMap<String,serde_json::Value> = Default::default();
219+
if let Some(previous_version_doc_url) = obj.ext_previous_version_doc_url() {
220+
extensions.insert("x-previousVersionUrl".to_string(), serde_json::json!(previous_version_doc_url));
221+
}
218222
ExternalDocumentation {
219223
description: None,
220224
url: url.trim().replace("{branch}", branch),
221-
extensions: Default::default(),
225+
extensions,
222226
}
223227
})
224228
}
3.42 KB
Binary file not shown.

compiler-rs/openapi_to_clients_schema/src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ fn generate_interface_def(
404404
doc_url: None,
405405
ext_doc_id: None,
406406
ext_doc_url: None,
407+
ext_previous_version_doc_url: None,
407408
codegen_name: None, // FIXME: extension in workplace search
408409
description: None,
409410
aliases: Vec::default(),

compiler/src/model/metamodel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ export class Endpoint {
443443
docId?: string
444444
extDocId?: string
445445
extDocUrl?: string
446+
extPreviousVersionDocUrl?: string
446447
deprecation?: Deprecation
447448
availability: Availabilities
448449
docTag?: string

compiler/src/model/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,9 @@ export function hoistRequestAnnotations (
694694
const docUrl = docIds.find(entry => entry[0] === value.trim())
695695
assert(jsDocs, docUrl != null, `The @doc_id '${value.trim()}' is not present in _doc_ids/table.csv`)
696696
endpoint.docUrl = docUrl[1].replace(/\r/g, '')
697+
if (docUrl[2].replace(/\r/g, '') !== '') {
698+
endpoint.extPreviousVersionDocUrl = docUrl[2].replace(/\r/g, '')
699+
}
697700
} else if (tag === 'ext_doc_id') {
698701
assert(jsDocs, value.trim() !== '', `Request ${request.name.name}'s @ext_doc_id cannot be empty`)
699702
endpoint.extDocId = value.trim()

docs/add-new-api.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
It might happen that a new API in Elasticsearch is not yet defined
44
in this repository, or we do have an endpoint definition in [`/specification/_json_spec`](../specification/_json_spec)
55
but we don't have a type definition for it.
6-
In this document you will see how to add a new endpopint and how to add a new endpoint definition.
6+
In this document you will see how to add a new endpoint and how to add a new endpoint definition.
77

88
## How to add a new endpoint
99

10-
Add a new endpoint is straightforward, you only need to copy-paste the json rest-api-spec defintion
10+
Add a new endpoint is straightforward, you only need to copy-paste the json rest-api-spec definition
1111
from the Elasticsearch repository inside [`/specification/_json_spec`](../specification/_json_spec)
1212
and you are good to go.
1313

@@ -17,10 +17,10 @@ or [here](https://github.com/elastic/elasticsearch/tree/7.x/x-pack/plugin/src/te
1717
## How to add the definition of an endpoint
1818

1919
Once you have added a new endpoint definition, the next step is to add its type definition.
20-
First of all, you should find the most approariate place inside [`/specification`](../specification)
20+
First of all, you should find the most appropriate place inside [`/specification`](../specification)
2121
where to put the new definition. The content of [`/specification`](../specification)
22-
tryied to mimic the Elasticsearch online documentation, so you can use it as inspiration.
23-
For example, the index document defintion can be found in [`/specification/_global/index`](../specification/_global/index).
22+
tried to mimic the Elasticsearch online documentation, so you can use it as inspiration.
23+
For example, the index document definition can be found in [`/specification/_global/index`](../specification/_global/index).
2424

2525
Once you have found the best place for the new definition, you should create a new file for it.
2626
The filename should be the same of the type definition you are writing, for example:
@@ -40,16 +40,16 @@ you can define it in the same file where it's used, unless is a commonly used ty
4040

4141
### Add the endpoint request definition
4242

43-
Request definitions are slighly different from other definitions.
43+
Request definitions are slightly different from other definitions.
4444
It is required that the request definition is named `Request`.
45-
A request definition is an interface and should contains three top level keys:
45+
A request definition is an interface and should contain these top level keys:
4646

4747
- `urls`: the URL paths templates and allowed HTTP methods
4848
- `path_parts`: the path parameters (eg: `indices`, `id`...)
4949
- `query_parameters`: the query parameters (eg: `timeout`, `pipeline`...)
5050
- `body`: the body parameters (eg: `query` or user defined entities)
5151

52-
Furthermore, every request definition **must** contain three JS Doc tags:
52+
Furthermore, every request definition **must** contain these JS Doc tags:
5353

5454
- `@rest_spec_name`: the API name (eg: `search`, `indices.create`...).
5555
- `@availability` Which flavor of Elasticsearch is this API available for? (eg `stack` or `serverless`)
@@ -130,7 +130,7 @@ class Response {
130130
```
131131

132132
As you can see, for responses there are no custom top level keys, as the
133-
response definition represents the body of a succesful response.
133+
response definition represents the body of a successful response.
134134

135135
#### Generics
136136

@@ -172,7 +172,7 @@ class Response {
172172
Add an `examples` folder and `request` and `xxx_response` subfolders (where `xxx` is the relevant response code). The file names within these folders should be unique (for example,`DownsampleRequestExample1.yaml` not `RequestExample1.yaml`).
173173

174174
These examples are for use in the API documentation and must adhere to the [OpenAPI 3.0 Example object specification](https://spec.openapis.org/oas/v3.0.3#example-object). They must have a `value` field that contains the request or response body.
175-
If there are multiple examples for the endpoint, they must each have a brief `summary` field, which is used as the label for the example. You can also optionaly provide an explanation in a `description` field.
175+
If there are multiple examples for the endpoint, they must each have a brief `summary` field, which is used as the label for the example. You can also optionally provide an explanation in a `description` field.
176176
In order to generate curl and console examples automatically, the request examples must also contain a `method_request`.
177177

178178
For example:
@@ -197,5 +197,5 @@ value: |-
197197
```
198198
199199
NOTE: A good example covers a very common use case or demonstrates a more complex but critical use case.
200-
It involves realistic data sets ( rather than generic "hello world" values).
200+
It involves realistic data sets (rather than generic "hello world" values).
201201
If it requires detailed setup or explanations, however, it is more appropriate for coverage in longer-form narrative documentation.

docs/modeling-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ property: UserDefinedValue
148148

149149
### Numbers
150150

151-
The numeric type in TypeScript is `number`, but given that this specification targets mutliple languages,
151+
The numeric type in TypeScript is `number`, but given that this specification targets multiple languages,
152152
it offers a bunch of aliases that represent the type that should be used if the language supports it:
153153

154154
```ts
@@ -504,7 +504,7 @@ Code generators should track the `es_quirk` they implement and fail if a new unh
504504
505505
### Additional information
506506
507-
If needed, you can specify additional information on each type with the approariate JSDoc tag.
507+
If needed, you can specify additional information on each type with the appropriate JSDoc tag.
508508
Following you can find a list of the supported tags:
509509
510510
#### `@availability`

docs/specification-structure.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ end with `Request` or `Response`.
3030
### Request and Response definitions
3131

3232
Request and Response definitions should be placed by strictly following
33+
the rest-api-spec structure.
3334
the `rest-api-spec` structure.
3435
For example, the request and response definition for `indices.put_mapping`
3536
should go in [`/specification/indices/put_mapping`](../specification/indices/put_mapping).

0 commit comments

Comments
 (0)