report/reporter.rb (94 lines of code) (raw):
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require_relative 'lib/api_endpoint'
module Elastic
# Generates API report.
#
# Usage:
# @reporter = Elastic::Reporter.new
# template = ERB.new(File.read('./template.erb'), trim_mode: '-')
# File.write('../apis_report.md', template.result(binding))
#
class Reporter
STACK_FILES = "#{File.expand_path('./tmp/rest-api-spec/api')}/*.json".freeze
# APIs designed for indirect use by ECE/ESS and ECK, direct use is not supported.'
EXCLUDED_APIS = [
{ name: 'autoscaling', reason: 'Designed for indirect use by ECE/ESS and ECK. Direct use is not supported.' },
{ name: 'shutdown', reason: 'Designed for indirect use by ECE/ESS and ECK. Direct use is not supported.' },
{ name: 'rollup', reason: 'The rollup feature was never GA-ed and is still tech preview. It has been deprecated since 8.11.0 in favor of downsampling.' },
{ name: 'knn_search', reason: 'It was only ever experimental and was deprecated in v`8.4`. It isn\'t supported in 9.0, and only works when the header `compatible-with=8` is set.' }
].freeze
attr_reader :endpoints, :internal, :json_spec
def initialize
@endpoints = []
@internal = []
puts '⏳ Reading and parsing specifications...'
build_elasticsearch_specification
build_json_apis
end
# Serverless APIs are obtained from elastic/elasticsearch-specification.
# Use `rake download_serverless` to download the files to ../tmp.
#
def build_elasticsearch_specification
JSON.parse(File.read('./tmp/schema.json'))['endpoints'].map do |spec|
if spec['name'].start_with?('_')
@internal << { name: spec['name'], reason: 'Internal API' }
elsif (skippable = EXCLUDED_APIS.select { |api| spec['name'].match? api[:name] }.first)
@internal << skippable
elsif spec.dig('availability', 'stack', 'visibility') == 'private'
@internal << { name: spec['name'], reason: 'Private API' }
else
@endpoints << ApiEndpoint.new(spec)
end
end
end
# Stack APIs are obtained from the Elasticsearch Rest JSON specification.
# Use `rake download_stack` to download the spec files to ../tmp.
#
# It is assumed that all the APIs in elasticsearch-specification are already present in the
# Elasticsearch JSON specification.
#
def build_json_apis
@json_spec = {
internal: [],
apis: [],
exclusive: []
}
# Find all the JSON files with API specifications:
apis = Dir[STACK_FILES].map { |path| path.split('/').last.gsub('.json', '') }
apis.each do |name|
# Skip if it's an internal or excluded API:
if name.start_with?('_') || EXCLUDED_APIS.select { |api| name.match? api[:name] }.any?
@json_spec[:internal] << name
elsif (endpoint = @endpoints.find { |e| e.name == name })
tested = ApiEndpoint::find_rest_api_test(name)
@json_spec[:apis] << { name: name, tested: tested }
# If we find this API in the spec, add the metadata to the object in @endpoints
endpoint.test_elasticsearch = tested
else
# If the API is not in elasticsearch-specification, add it to only ES:
@json_spec[:exclusive] << { name: name, tested: ApiEndpoint::find_rest_api_test(name) }
end
end
end
def coverage_rest_api
@json_spec[:apis].count { |a| a[:tested] } * 100 / @json_spec[:apis].count
end
# Calculates what percentage of serverless endpoints are being tested
#
def coverage_serverless
@endpoints.count(&:tested_serverless?) * 100 / @endpoints.count(&:available_serverless?)
end
# Calculates what percentage of serverless endpoints are being tested
#
def coverage_stack
@endpoints.count(&:tested_stack?) * 100 / @endpoints.count(&:available_stack?)
end
# Calculates how many stack endpoints are not being tested
#
def untested_stack_count
@endpoints.count { |api| api.available_stack? && !api.tested_stack? }
end
# Calculates how many serverless endpoints are not being tested
#
def untested_serverless_count
@endpoints.count do |api|
api.available_serverless? && !api.tested_serverless?
end
end
# Helper for the template
# It displays a Markdown table with the information for each endpoint
#
def display_table
@endpoints.map do |endpoint|
"| #{endpoint.name} | #{endpoint.available_stack? ? '🟢' : '🔴'} " \
"| #{endpoint.display_tested_stack} | #{endpoint.display_tested_elasticsearch}" \
"| #{endpoint.available_serverless? ? '🟢' : '🔴'} " \
"| #{endpoint.display_tested_serverless}"
end.join("\n")
end
def namespaces_stack
@namespaces_stack ||= namespaces(:stack)
end
def namespaces_serverless
@namespaces_serverless ||= namespaces(:serverless)
end
def namespaces(flavour = nil)
if flavour
endpoints.map do |a|
a.name.split('.').first if a.name.include?('.') && a.send("available_#{flavour}?")
end.compact.uniq
else
endpoints.map do |a|
a.name.split('.').first if a.name.include?('.')
end.compact.uniq
end
end
end
end