From 991e7b3e33ce65d589bff938491a07c9ccd1db59 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 01:19:50 +0200 Subject: [PATCH 1/9] Merge properties and required fields in allOf composition --- lib/generator.ml | 30 +++++++++++++++++++++++++++++- lib/json_schema.atd | 2 +- tests/all_of.ml | 45 +++++++++++++++++++++++---------------------- tests/dune | 1 + 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index 01c6567..f2b8c5b 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -36,7 +36,32 @@ let make_atd_default_value enum json_value = let nullable = Printf.sprintf "%s nullable" +let nonempty_list_opt = function + | [] -> None + | non_empty_list -> Some non_empty_list + +let rec merge_all_of schema = + match schema.all_of with + | None -> schema + | Some [] -> failwith "empty allOf is unexpected" + | Some schemas -> + let all_schemas = + schema + :: List.filter_map + (function + | Obj schema -> Some (merge_all_of schema) + | Ref _ -> None + ) + schemas + in + { + schema with + properties = all_schemas |> List.filter_map (fun schema -> schema.properties) |> List.flatten |> nonempty_list_opt; + required = all_schemas |> List.map (fun schema -> schema.required) |> List.flatten; + } + let rec process_schema_type ~ancestors (schema : schema) = + let schema = merge_all_of schema in let maybe_nullable type_ = if schema.nullable then nullable type_ else type_ in match schema.one_of with | Some schemas -> process_one_of ~ancestors schemas @@ -134,6 +159,7 @@ type int64 = int let make_atd_of_jsonschema input = let schema = Json_schema_j.schema_of_string input in let root_type_name = Option.value ~default:"root" schema.title in + Buffer.clear toplevel_definitions; base ^ "\n" ^ process_schemas [ root_type_name, Obj schema ] let make_atd_of_openapi input = @@ -142,5 +168,7 @@ let make_atd_of_openapi input = | None -> failwith "components are empty" | Some components -> match components.schemas with - | Some schemas -> base ^ "\n" ^ process_schemas schemas + | Some schemas -> + Buffer.clear toplevel_definitions; + base ^ "\n" ^ process_schemas schemas | None -> failwith "components schemas are empty" diff --git a/lib/json_schema.atd b/lib/json_schema.atd index 2ec3cb7..8001e2a 100644 --- a/lib/json_schema.atd +++ b/lib/json_schema.atd @@ -40,7 +40,7 @@ type schema = { ~schema : string nullable; (* 10.2.1 keywords for applying subschemas with logic *) - ~all_of : schema nonempty_list nullable; + ~all_of : schema or_ref nonempty_list nullable; ~any_of : schema nonempty_list nullable; ~one_of : schema or_ref nonempty_list nullable; ~not : schema nullable; diff --git a/tests/all_of.ml b/tests/all_of.ml index 5978e35..2e73d68 100644 --- a/tests/all_of.ml +++ b/tests/all_of.ml @@ -5,32 +5,32 @@ let simple_test _ = let input = {| { "dummy": { - "type": "object", - "properties": { - "field": { - "id": "string" - } - }, - "allOf": [ - { - "type": "object", - "properties": { - "field": { - "name": "string" + "type": "object", + "properties": { + "id": { + "type": "string" } - } }, - { - "type": "object", - "properties": { - "field": { - "surname": "string" + "allOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "surname": { + "type": "string" + } + } } - } - } - ] + ] } -}|} +} |} in let output = {| @@ -38,6 +38,7 @@ let simple_test _ = ?id: string option; ?name: string option; ?surname: string option; + } |} in assert_schema input output diff --git a/tests/dune b/tests/dune index f01de6e..b1f1878 100644 --- a/tests/dune +++ b/tests/dune @@ -1,5 +1,6 @@ (tests (names + all_of base defaults enums From 43f38b15c622aa2dcce57c6f490311fb50949846 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 16:43:17 +0200 Subject: [PATCH 2/9] Deeper merge allOf schemes WIP --- lib/generator.ml | 102 +++++++++++++++++++++++++++++++++++--------- lib/json_schema.atd | 3 ++ lib/utils.ml | 6 +++ 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index f2b8c5b..b79e371 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -14,10 +14,34 @@ let process_int_type schema = | Some `Int64 -> "int64" | _ -> failwith "int has unextected format" -let get_ref (ref : ref_) = - match ref |> String.split_on_char '/' |> List.rev with - | type_name :: _ -> type_name - | _ -> failwith (Printf.sprintf "%s: can't resolve ref type name" ref) +let get_ref_name (ref : ref_) = + match String.split_on_char '/' ref with + (* OpenAPI defs *) + | [ "#"; "components"; "schemas"; type_name ] -> type_name + (* JSON Schema defs *) + | [ "#"; "$defs"; type_name ] -> type_name + | _ -> + failwith + (Printf.sprintf "Unsupported ref value: %s. Supported ref URI are: #/components/schemas/* and #/$defs/*" ref) + +let toplevel_definitions = Buffer.create 16 +let global_defs = ref [] +let schemas_to_obj = List.map (fun (name, schema) -> name, Obj schema) + +let rec get_ref_schema ~schema ref = + let defs = + match schema.defs with + | None -> !global_defs + | Some defs -> schemas_to_obj defs @ !global_defs + in + List.find_map + (fun (name, schema_or_ref) -> + match schema_or_ref with + | Obj schema when String.equal (get_ref_name ref) name -> Some schema + | Obj _ -> None + | Ref ref -> get_ref_schema ~schema ref + ) + defs let rec ocaml_value_of_json = function | (`Bool _ | `Float _ | `Int _ | `Null) as json -> Yojson.Basic.to_string json @@ -44,20 +68,58 @@ let rec merge_all_of schema = match schema.all_of with | None -> schema | Some [] -> failwith "empty allOf is unexpected" - | Some schemas -> - let all_schemas = - schema - :: List.filter_map - (function - | Obj schema -> Some (merge_all_of schema) - | Ref _ -> None - ) - schemas + | Some all_of -> + let ref_schemas = + List.filter_map + (function + | Obj schema -> Some (merge_all_of schema) + | Ref ref -> get_ref_schema ~schema ref + ) + all_of in + let schemas = schema :: ref_schemas in + let take_opt get_fn = + match schemas |> List.filter_map get_fn with + | [] -> None + | first :: _ -> Some first + (* | _other -> failwith (Printf.sprintf "%s overwrite allOf attributes are not allowed" (Json_schema_j.string_of_schema schema)) *) + in + let merge_lists get_fn = schemas |> List.map get_fn |> List.flatten in + let merge_opt_lists get_fn = schemas |> List.filter_map get_fn |> List.flatten |> nonempty_list_opt in { - schema with - properties = all_schemas |> List.filter_map (fun schema -> schema.properties) |> List.flatten |> nonempty_list_opt; - required = all_schemas |> List.map (fun schema -> schema.required) |> List.flatten; + schema = take_opt (fun schema -> schema.schema); + all_of = merge_opt_lists (fun schema -> schema.all_of); + any_of = merge_opt_lists (fun schema -> schema.any_of); + one_of = merge_opt_lists (fun schema -> schema.one_of); + not = take_opt (fun schema -> schema.not); + items = take_opt (fun schema -> schema.items); + properties = merge_opt_lists (fun schema -> schema.properties); + additional_properties = take_opt (fun schema -> schema.additional_properties); + typ = take_opt (fun schema -> schema.typ); + enum = + schemas + |> List.filter_map (fun schema -> schema.enum) + |> Utils.shortest_list + |> Option.value ~default:[] + |> nonempty_list_opt; + max_length = take_opt (fun schema -> schema.max_length); + min_length = take_opt (fun schema -> schema.min_length); + pattern = take_opt (fun schema -> schema.pattern); + max_items = take_opt (fun schema -> schema.max_items); + min_items = take_opt (fun schema -> schema.min_items); + unique_items = take_opt (fun schema -> schema.unique_items); + max_contains = take_opt (fun schema -> schema.max_contains); + min_contains = take_opt (fun schema -> schema.min_contains); + max_properties = take_opt (fun schema -> schema.max_properties); + min_properties = take_opt (fun schema -> schema.min_properties); + required = merge_lists (fun schema -> schema.required); + dependent_required = merge_lists (fun schema -> schema.dependent_required); + format = take_opt (fun schema -> schema.format); + defs = merge_opt_lists (fun schema -> schema.defs); + title = take_opt (fun schema -> schema.title); + description = take_opt (fun schema -> schema.description); + default = take_opt (fun schema -> schema.default); + nullable = schemas |> List.exists (fun schema -> schema.nullable); } let rec process_schema_type ~ancestors (schema : schema) = @@ -87,8 +149,6 @@ and process_array_type ~ancestors schema = | Some schema_or_ref -> [ make_type_from_schema_or_ref ~ancestors schema_or_ref; "list" ] | None -> failwith "items is not specified for array" -and toplevel_definitions = Buffer.create 16 - and process_nested_schema_type ~ancestors schema = match schema with | { one_of = Some _; _ } | { typ = Some Object; properties = Some _; _ } | { enum = Some _; _ } -> @@ -117,11 +177,11 @@ and make_type_from_schema_or_ref ~ancestors (schema_or_ref : schema or_ref) = match schema_or_ref, ancestors with | Obj schema, ([] | [ _ ]) -> process_schema_type ~ancestors schema | Obj schema, ancestors -> process_nested_schema_type ~ancestors schema - | Ref ref_, _ -> type_name (get_ref ref_) + | Ref ref_, _ -> type_name (get_ref_name ref_) and process_one_of ~ancestors (schemas_or_refs : schema or_ref list) = let determine_variant_name = function - | Ref ref_ -> variant_name (get_ref ref_) + | Ref ref_ -> variant_name (get_ref_name ref_) | Obj schema -> match schema.typ with | Some Array -> concat_camelCase (process_array_type ~ancestors schema) @@ -160,6 +220,7 @@ let make_atd_of_jsonschema input = let schema = Json_schema_j.schema_of_string input in let root_type_name = Option.value ~default:"root" schema.title in Buffer.clear toplevel_definitions; + Option.iter (fun defs -> global_defs := schemas_to_obj defs) schema.defs; base ^ "\n" ^ process_schemas [ root_type_name, Obj schema ] let make_atd_of_openapi input = @@ -170,5 +231,6 @@ let make_atd_of_openapi input = match components.schemas with | Some schemas -> Buffer.clear toplevel_definitions; + global_defs := schemas; base ^ "\n" ^ process_schemas schemas | None -> failwith "components schemas are empty" diff --git a/lib/json_schema.atd b/lib/json_schema.atd index 8001e2a..82b5095 100644 --- a/lib/json_schema.atd +++ b/lib/json_schema.atd @@ -86,6 +86,9 @@ type schema = { (* 7. semantic content with "format" *) ~format : format nullable; + (* 8.2.4. re-usable JSON Schemas *) + ~defs : (string * schema) list nullable; + (* 9. basic metadata annotations *) ~title : string nullable; ~description : string nullable; diff --git a/lib/utils.ml b/lib/utils.ml index fff2a71..5973749 100644 --- a/lib/utils.ml +++ b/lib/utils.ml @@ -88,3 +88,9 @@ struct let compare = T.compare let equal a b = T.compare a b = 0 end + +let hd_opt = function + | [] -> None + | first :: _ -> Some first + +let shortest_list lists = lists |> List.sort (fun a b -> compare (List.length a) (List.length b)) |> hd_opt From 1d42640b9e88978df7476eb74356df9bea58496c Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 18:27:23 +0200 Subject: [PATCH 3/9] Merge more fields in allOf --- lib/generator.ml | 52 +++++++++++++++++++--------------------- tests/all_of.ml | 62 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index b79e371..7621e8a 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -64,7 +64,7 @@ let nonempty_list_opt = function | [] -> None | non_empty_list -> Some non_empty_list -let rec merge_all_of schema = +let merge_all_of schema = match schema.all_of with | None -> schema | Some [] -> failwith "empty allOf is unexpected" @@ -72,53 +72,52 @@ let rec merge_all_of schema = let ref_schemas = List.filter_map (function - | Obj schema -> Some (merge_all_of schema) + | Obj schema -> Some schema | Ref ref -> get_ref_schema ~schema ref ) all_of in let schemas = schema :: ref_schemas in - let take_opt get_fn = + let take_first_opt get_fn = match schemas |> List.filter_map get_fn with | [] -> None | first :: _ -> Some first - (* | _other -> failwith (Printf.sprintf "%s overwrite allOf attributes are not allowed" (Json_schema_j.string_of_schema schema)) *) in let merge_lists get_fn = schemas |> List.map get_fn |> List.flatten in let merge_opt_lists get_fn = schemas |> List.filter_map get_fn |> List.flatten |> nonempty_list_opt in { - schema = take_opt (fun schema -> schema.schema); + schema with + schema = take_first_opt (fun schema -> schema.schema); all_of = merge_opt_lists (fun schema -> schema.all_of); any_of = merge_opt_lists (fun schema -> schema.any_of); one_of = merge_opt_lists (fun schema -> schema.one_of); - not = take_opt (fun schema -> schema.not); - items = take_opt (fun schema -> schema.items); + not = take_first_opt (fun schema -> schema.not); + items = take_first_opt (fun schema -> schema.items); properties = merge_opt_lists (fun schema -> schema.properties); - additional_properties = take_opt (fun schema -> schema.additional_properties); - typ = take_opt (fun schema -> schema.typ); + additional_properties = take_first_opt (fun schema -> schema.additional_properties); enum = schemas |> List.filter_map (fun schema -> schema.enum) |> Utils.shortest_list |> Option.value ~default:[] |> nonempty_list_opt; - max_length = take_opt (fun schema -> schema.max_length); - min_length = take_opt (fun schema -> schema.min_length); - pattern = take_opt (fun schema -> schema.pattern); - max_items = take_opt (fun schema -> schema.max_items); - min_items = take_opt (fun schema -> schema.min_items); - unique_items = take_opt (fun schema -> schema.unique_items); - max_contains = take_opt (fun schema -> schema.max_contains); - min_contains = take_opt (fun schema -> schema.min_contains); - max_properties = take_opt (fun schema -> schema.max_properties); - min_properties = take_opt (fun schema -> schema.min_properties); + max_length = take_first_opt (fun schema -> schema.max_length); + min_length = take_first_opt (fun schema -> schema.min_length); + pattern = take_first_opt (fun schema -> schema.pattern); + max_items = take_first_opt (fun schema -> schema.max_items); + min_items = take_first_opt (fun schema -> schema.min_items); + unique_items = take_first_opt (fun schema -> schema.unique_items); + max_contains = take_first_opt (fun schema -> schema.max_contains); + min_contains = take_first_opt (fun schema -> schema.min_contains); + max_properties = take_first_opt (fun schema -> schema.max_properties); + min_properties = take_first_opt (fun schema -> schema.min_properties); required = merge_lists (fun schema -> schema.required); dependent_required = merge_lists (fun schema -> schema.dependent_required); - format = take_opt (fun schema -> schema.format); + format = take_first_opt (fun schema -> schema.format); defs = merge_opt_lists (fun schema -> schema.defs); - title = take_opt (fun schema -> schema.title); - description = take_opt (fun schema -> schema.description); - default = take_opt (fun schema -> schema.default); + title = take_first_opt (fun schema -> schema.title); + description = take_first_opt (fun schema -> schema.description); + default = take_first_opt (fun schema -> schema.default); nullable = schemas |> List.exists (fun schema -> schema.nullable); } @@ -138,8 +137,7 @@ let rec process_schema_type ~ancestors (schema : schema) = | Some String -> maybe_nullable "string" | Some Boolean -> maybe_nullable "bool" | Some Array -> maybe_nullable (process_array_type ~ancestors schema |> String.concat " ") - | Some Object -> - if schema.nullable then process_nested_schema_type ~ancestors schema else process_object_type ~ancestors schema + | Some Object -> process_object_type ~ancestors schema | None -> (* fallback to untyped if schema type is not defined *) maybe_nullable "json" @@ -150,13 +148,13 @@ and process_array_type ~ancestors schema = | None -> failwith "items is not specified for array" and process_nested_schema_type ~ancestors schema = - match schema with + match merge_all_of schema with | { one_of = Some _; _ } | { typ = Some Object; properties = Some _; _ } | { enum = Some _; _ } -> let nested_type_name = concat_camelCase (List.rev ancestors) in let nested = define_top_level nested_type_name (process_schema_type ~ancestors schema) in Buffer.add_string toplevel_definitions nested; type_name nested_type_name - | _ as schema -> process_schema_type ~ancestors schema + | _ -> process_schema_type ~ancestors schema and process_object_type ~ancestors schema = let is_required field_name = List.exists (String.equal field_name) schema.required in diff --git a/tests/all_of.ml b/tests/all_of.ml index 2e73d68..182afcb 100644 --- a/tests/all_of.ml +++ b/tests/all_of.ml @@ -43,5 +43,65 @@ let simple_test _ = in assert_schema input output -let suite = "allOf" >::: [ "simple test" >:: simple_test ] + +let with_nested _ = + let input = + {| { + "MappingType": { + "type": "string", + "enum": [ + "value", + "range", + "regex", + "special" + ] + }, + "RangeMap": { + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "type": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/MappingType" + }, + { + "enum": [ + "range" + ] + } + ] + } + } + } +} |} + in + let output = + {| + type rangeMapType = [ + | Range + ] + + type rangeMap = { + type_ : rangeMapType; + } + + type mappingType = [ + | Value + | Range + | Regex + | Special + ] + |} + in + assert_schema input output + +let suite = "allOf" >::: [ + "simple test" >:: simple_test; + "with nested" >:: with_nested + ] let () = run_test_tt_main suite From bd74b7d7c64afde424a514468e8a9febcdbbab58 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 18:31:00 +0200 Subject: [PATCH 4/9] Fmt --- tests/all_of.ml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/all_of.ml b/tests/all_of.ml index 182afcb..d5ddf18 100644 --- a/tests/all_of.ml +++ b/tests/all_of.ml @@ -43,7 +43,6 @@ let simple_test _ = in assert_schema input output - let with_nested _ = let input = {| { @@ -100,8 +99,5 @@ let with_nested _ = in assert_schema input output -let suite = "allOf" >::: [ - "simple test" >:: simple_test; - "with nested" >:: with_nested - ] +let suite = "allOf" >::: [ "simple test" >:: simple_test; "with nested" >:: with_nested ] let () = run_test_tt_main suite From d6ee0bd03d5db76dfef8ebab5440014b81940180 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 18:34:18 +0200 Subject: [PATCH 5/9] Fix test formatting --- tests/all_of.ml | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/all_of.ml b/tests/all_of.ml index d5ddf18..de5761c 100644 --- a/tests/all_of.ml +++ b/tests/all_of.ml @@ -3,34 +3,34 @@ open Base let simple_test _ = let input = - {| { + {|{ "dummy": { - "type": "object", - "properties": { - "id": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "allOf": [ + { + "type": "object", + "properties": { + "name": { "type": "string" } + } }, - "allOf": [ - { - "type": "object", - "properties": { - "name": { - "type": "string" - } - } - }, - { - "type": "object", - "properties": { - "surname": { - "type": "string" - } - } + { + "type": "object", + "properties": { + "surname": { + "type": "string" } - ] + } + } + ] } -} |} +}|} in let output = {| @@ -45,7 +45,7 @@ let simple_test _ = let with_nested _ = let input = - {| { + {|{ "MappingType": { "type": "string", "enum": [ @@ -77,7 +77,7 @@ let with_nested _ = } } } -} |} +}|} in let output = {| From 6df03fbdaa3bf07549633d833abefd4183b22b69 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 18:39:04 +0200 Subject: [PATCH 6/9] Some clean up --- lib/generator.ml | 8 ++------ lib/utils.ml | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index 7621e8a..7e7153f 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -60,10 +60,6 @@ let make_atd_default_value enum json_value = let nullable = Printf.sprintf "%s nullable" -let nonempty_list_opt = function - | [] -> None - | non_empty_list -> Some non_empty_list - let merge_all_of schema = match schema.all_of with | None -> schema @@ -84,7 +80,7 @@ let merge_all_of schema = | first :: _ -> Some first in let merge_lists get_fn = schemas |> List.map get_fn |> List.flatten in - let merge_opt_lists get_fn = schemas |> List.filter_map get_fn |> List.flatten |> nonempty_list_opt in + let merge_opt_lists get_fn = schemas |> List.filter_map get_fn |> List.flatten |> Utils.nonempty_list_opt in { schema with schema = take_first_opt (fun schema -> schema.schema); @@ -100,7 +96,7 @@ let merge_all_of schema = |> List.filter_map (fun schema -> schema.enum) |> Utils.shortest_list |> Option.value ~default:[] - |> nonempty_list_opt; + |> Utils.nonempty_list_opt; max_length = take_first_opt (fun schema -> schema.max_length); min_length = take_first_opt (fun schema -> schema.min_length); pattern = take_first_opt (fun schema -> schema.pattern); diff --git a/lib/utils.ml b/lib/utils.ml index 5973749..9c80728 100644 --- a/lib/utils.ml +++ b/lib/utils.ml @@ -94,3 +94,7 @@ let hd_opt = function | first :: _ -> Some first let shortest_list lists = lists |> List.sort (fun a b -> compare (List.length a) (List.length b)) |> hd_opt + +let nonempty_list_opt = function + | [] -> None + | non_empty_list -> Some non_empty_list From 432e6b189b3d490b8db20cffb5fd3698b5d4da30 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Fri, 29 Dec 2023 18:40:39 +0200 Subject: [PATCH 7/9] Mark allOf as implemented --- README.md | 2 +- tests/grok.t | 492 ++++++++ tests/mocks/dashboard_types_gen.json | 1611 ++++++++++++++++++++++++++ 3 files changed, 2104 insertions(+), 1 deletion(-) create mode 100644 tests/grok.t create mode 100644 tests/mocks/dashboard_types_gen.json diff --git a/README.md b/README.md index fd832f6..07621fa 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,4 @@ You can call `jsonschema2atd` and `atdgen` in your `dune` file to generate OCaml - [X] OneOf (Only serialization is supported) - [ ] not - [ ] anyOf -- [ ] allOf +- [X] allOf diff --git a/tests/grok.t b/tests/grok.t new file mode 100644 index 0000000..ceccb09 --- /dev/null +++ b/tests/grok.t @@ -0,0 +1,492 @@ +Generate ATD types from grok (Grafana Object Development Kit) dashboard types + $ jsonschema2atd --format openapi ./mocks/dashboard_types_gen.json + + type json = abstract + type int64 = int + + type fieldConfigSourceOverrides = { + matcher: matcherConfig; + properties: dynamicConfigValue list; + } + + type graphPanelType = [ + | Graph + ] + + type graphPanelLegend = { + ~show : bool; + ?sort: string option; + ?sortDesc: bool option; + } + + type heatmapPanelType = [ + | Heatmap + ] + + type panelRepeatDirection = [ + | H + | V + ] + + type rangeMapType = [ + | Range + ] + + type rangeMapOptions = { + from: float; + to_ : float; + result: valueMappingResult; + } + + type regexMapType = [ + | Regex + ] + + type regexMapOptions = { + pattern: string; + result: valueMappingResult; + } + + type rowPanelType = [ + | Row + ] + + type rowPanelPanels = [ + | Panel of panel + | GraphPanel of graphPanel + | HeatmapPanel of heatmapPanel + ] + + type specialValueMapType = [ + | Special + ] + + type specialValueMapOptions = { + match_ : specialValueMatch; + result: valueMappingResult; + } + + type valueMapType = [ + | Value + ] + + type variableModelQuery = [ + | String of string + | Json of json + ] + + type variableOptionText = [ + | String of string + | StringList of string list + ] + + type variableOptionValue = [ + | String of string + | StringList of string list + ] + + type dashboardMetadata = { + updateTimestamp: string; + createdBy: string; + updatedBy: string; + extraFields: json; + uid: string; + creationTimestamp: string; + ?deletionTimestamp: string option; + finalizers: string list; + resourceVersion: string; + labels: json; + } + + type dashboardSpecTime = { + ~from : string; + ~to_ : string; + } + + type dashboardSpecTimepicker = { + ~hidden : bool; + ~refresh_intervals : string list; + ~collapse : bool; + ~time_options : string list; + } + + type dashboardSpecRefresh = [ + | Bool of bool + | String of string + ] + + type dashboardSpecPanels = [ + | Json of json + | RowPanel of rowPanel + | GraphPanel of graphPanel + | HeatmapPanel of heatmapPanel + ] + + type dashboardSpecTemplating = { + ?list: variableModel list option; + } + + type dashboardSpec = { + ?id: int option; + ?uid: string option; + ?title: string option; + ?description: string option; + ?revision: int64 option; + ?gnetId: string option; + ?tags: string list option; + ~timezone : string; + ~editable : bool; + ?graphTooltip: dashboardCursorSync option; + ?time: dashboardSpecTime option; + ?timepicker: dashboardSpecTimepicker option; + ~fiscalYearStartMonth : int; + ?liveNow: bool option; + ?weekStart: string option; + ?refresh: dashboardSpecRefresh option; + ~schemaVersion : int; + ?version: int option; + ?panels: dashboardSpecPanels list option; + ?templating: dashboardSpecTemplating option; + ?annotations: annotationContainer option; + ?links: dashboardLink list option; + ?snapshot: snapshot option; + } + + type dashboardStatus = { + ?operatorStates: json option; + ?additionalFields: json option; + } + + type statusOperatorStateState = [ + | Success + | In_progress + | Failed + ] + + type statusOperatorState = { + lastEvaluation: string; + state: statusOperatorStateState; + ?descriptiveState: string option; + ?details: json option; + } + + type dashboard = { + metadata: dashboardMetadata; + spec: dashboardSpec; + status: dashboardStatus; + } + + type _kubeObjectMetadata = { + uid: string; + creationTimestamp: string; + ?deletionTimestamp: string option; + finalizers: string list; + resourceVersion: string; + labels: json; + } + + type variableType = [ + | Query + | Adhoc + | Constant + | Datasource + | Interval + | Textbox + | Custom + | System + ] + + type variableSort = int + + type variableRefresh = int + + type variableOption = { + ?selected: bool option; + text: variableOptionText; + value: variableOptionValue; + } + + type variableModel = { + type_ : variableType; + name: string; + ?label: string option; + ?hide: variableHide option; + ~skipUrlSync : bool; + ?description: string option; + ?query: variableModelQuery option; + ?datasource: dataSourceRef option; + ?current: variableOption option; + ~multi : bool; + ?options: variableOption list option; + ?refresh: variableRefresh option; + ?sort: variableSort option; + } + + type variableHide = int + + type valueMappingResult = { + ?text: string option; + ?color: string option; + ?icon: string option; + ?index: int option; + } + + type valueMapping = [ + | ValueMap of valueMap + | RangeMap of rangeMap + | RegexMap of regexMap + | SpecialValueMap of specialValueMap + ] + + type valueMap = { + type_ : valueMapType; + options: json; + } + + type thresholdsMode = [ + | Absolute + | Percentage + ] + + type thresholdsConfig = { + mode: thresholdsMode; + steps: threshold list; + } + + type threshold = { + value: float; + color: string; + } + + type target = json + + type specialValueMatch = [ + | True_ + | False_ + | Null + | Nan + | Nullnan + | Empty + ] + + type specialValueMap = { + type_ : specialValueMapType; + options: specialValueMapOptions; + } + + type snapshot = { + created: string; + expires: string; + external_ : bool; + externalUrl: string; + id: int; + key: string; + name: string; + orgId: int; + updated: string; + ?url: string option; + userId: int; + } + + type rowPanel = { + type_ : rowPanelType; + ~collapsed : bool; + ?title: string option; + ?datasource: dataSourceRef option; + ?gridPos: gridPos option; + id: int; + panels: rowPanelPanels list; + ?repeat: string option; + } + + type regexMap = { + type_ : regexMapType; + options: regexMapOptions; + } + + type rangeMap = { + type_ : rangeMapType; + options: rangeMapOptions; + } + + type panel = { + type_ : string; + ?id: int option; + ?pluginVersion: string option; + ?tags: string list option; + ?targets: target list option; + ?title: string option; + ?description: string option; + ~transparent : bool; + ?datasource: dataSourceRef option; + ?gridPos: gridPos option; + ?links: dashboardLink list option; + ?repeat: string option; + ~repeatDirection : panelRepeatDirection; + ?maxPerRow: float option; + ?maxDataPoints: float option; + ?transformations: dataTransformerConfig list option; + ?interval: string option; + ?timeFrom: string option; + ?timeShift: string option; + ?hideTimeOverride: bool option; + ?libraryPanel: libraryPanelRef option; + ?options: json option; + ?fieldConfig: fieldConfigSource option; + } + + type matcherConfig = { + ~id : string; + ?options: json option; + } + + type mappingType = [ + | Value + | Range + | Regex + | Special + ] + + type libraryPanelRef = { + name: string; + uid: string; + } + + type heatmapPanel = { + type_ : heatmapPanelType; + } + + type gridPos = { + ~h : int; + ~w : int; + ~x : int; + ~y : int; + ?static: bool option; + } + + type graphPanel = { + type_ : graphPanelType; + ?legend: graphPanelLegend option; + } + + type fieldConfigSource = { + defaults: fieldConfig; + overrides: fieldConfigSourceOverrides list; + } + + type fieldConfig = { + ?displayName: string option; + ?displayNameFromDS: string option; + ?description: string option; + ?path: string option; + ?writeable: bool option; + ?filterable: bool option; + ?unit: string option; + ?decimals: float option; + ?min: float option; + ?max: float option; + ?mappings: valueMapping list option; + ?thresholds: thresholdsConfig option; + ?color: fieldColor option; + ?links: json list option; + ?noValue: string option; + ?custom: json option; + } + + type fieldColorSeriesByMode = [ + | Min + | Max + | Last + ] + + type fieldColorModeId = [ + | Thresholds + | Paletteclassic + | Paletteclassicbyname + | ContinuousGrYlRd + | ContinuousRdYlGr + | ContinuousBlYlRd + | ContinuousYlRd + | ContinuousBlPu + | ContinuousYlBl + | Continuousblues + | Continuousreds + | Continuousgreens + | Continuouspurples + | Fixed + | Shades + ] + + type fieldColor = { + mode: fieldColorModeId; + ?fixedColor: string option; + ?seriesBy: fieldColorSeriesByMode option; + } + + type dynamicConfigValue = { + ~id : string; + ?value: json option; + } + + type dataTransformerConfig = { + id: string; + ?disabled: bool option; + ?filter: matcherConfig option; + options: json; + } + + type dataSourceRef = { + ?type_ : string option; + ?uid: string option; + } + + type dashboardLinkType = [ + | Link + | Dashboards + ] + + type dashboardLink = { + title: string; + type_ : dashboardLinkType; + icon: string; + tooltip: string; + url: string; + tags: string list; + ~asDropdown : bool; + ~targetBlank : bool; + ~includeVars : bool; + ~keepTime : bool; + } + + type dashboardCursorSync = int + + type annotationTarget = { + limit: int64; + matchAny: bool; + tags: string list; + type_ : string; + } + + type annotationQuery = { + name: string; + datasource: dataSourceRef; + ~enable : bool; + ~hide : bool; + iconColor: string; + ?filter: annotationPanelFilter option; + ?target: annotationTarget option; + ?type_ : string option; + ~builtIn : float; + } + + type annotationPanelFilter = { + ~exclude : bool; + ids: int list; + } + + type annotationContainer = { + ?list: annotationQuery list option; + } + + + diff --git a/tests/mocks/dashboard_types_gen.json b/tests/mocks/dashboard_types_gen.json new file mode 100644 index 0000000..a630fd2 --- /dev/null +++ b/tests/mocks/dashboard_types_gen.json @@ -0,0 +1,1611 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "dashboard", + "version": "0.0" + }, + "paths": {}, + "components": { + "schemas": { + "AnnotationContainer": { + "description": "Contains the list of annotations that are associated with the dashboard.\nAnnotations are used to overlay event markers and overlay event tags on graphs.\nGrafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API.\nSee https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/", + "type": "object", + "properties": { + "list": { + "description": "List of annotations", + "type": "array", + "items": { + "$ref": "#/components/schemas/AnnotationQuery" + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "AnnotationPanelFilter": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "exclude": { + "description": "Should the specified panels be included or excluded", + "type": "boolean", + "default": false + }, + "ids": { + "description": "Panel IDs that should be included or excluded", + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "AnnotationQuery": { + "description": "TODO docs\nFROM: AnnotationQuery in grafana-data/src/types/annotations.ts", + "type": "object", + "required": [ + "name", + "datasource", + "enable", + "iconColor" + ], + "properties": { + "name": { + "description": "Name of annotation.", + "type": "string" + }, + "datasource": { + "$ref": "#/components/schemas/DataSourceRef" + }, + "enable": { + "description": "When enabled the annotation query is issued with every dashboard refresh", + "type": "boolean", + "default": true + }, + "hide": { + "description": "Annotation queries can be toggled on or off at the top of the dashboard.\nWhen hide is true, the toggle is not shown in the dashboard.", + "type": "boolean", + "default": false + }, + "iconColor": { + "description": "Color to use for the annotation event markers", + "type": "string" + }, + "filter": { + "$ref": "#/components/schemas/AnnotationPanelFilter" + }, + "target": { + "$ref": "#/components/schemas/AnnotationTarget" + }, + "type": { + "description": "TODO -- this should not exist here, it is based on the --grafana-- datasource", + "type": "string" + }, + "builtIn": { + "description": "Set to 1 for the standard annotation query all dashboards have by default.", + "type": "number", + "default": 0 + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "AnnotationTarget": { + "description": "TODO: this should be a regular DataQuery that depends on the selected dashboard\nthese match the properties of the \"grafana\" datasouce that is default in most dashboards", + "type": "object", + "required": [ + "limit", + "matchAny", + "tags", + "type" + ], + "properties": { + "limit": { + "description": "Only required/valid for the grafana datasource...\nbut code+tests is already depending on it so hard to change", + "type": "integer", + "format": "int64" + }, + "matchAny": { + "description": "Only required/valid for the grafana datasource...\nbut code+tests is already depending on it so hard to change", + "type": "boolean" + }, + "tags": { + "description": "Only required/valid for the grafana datasource...\nbut code+tests is already depending on it so hard to change", + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "description": "Only required/valid for the grafana datasource...\nbut code+tests is already depending on it so hard to change", + "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DashboardCursorSync": { + "description": "0 for no shared crosshair or tooltip (default).\n1 for shared crosshair.\n2 for shared crosshair AND shared tooltip.", + "type": "integer", + "default": 0, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DashboardLink": { + "description": "Links with references to other dashboards or external resources", + "type": "object", + "required": [ + "title", + "type", + "icon", + "tooltip", + "url", + "tags", + "asDropdown", + "targetBlank", + "includeVars", + "keepTime" + ], + "properties": { + "title": { + "description": "Title to display with the link", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/DashboardLinkType" + }, + "icon": { + "description": "Icon name to be displayed with the link", + "type": "string" + }, + "tooltip": { + "description": "Tooltip to display when the user hovers their mouse over it", + "type": "string" + }, + "url": { + "description": "Link URL. Only required/valid if the type is link", + "type": "string" + }, + "tags": { + "description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards", + "type": "array", + "items": { + "type": "string" + } + }, + "asDropdown": { + "description": "If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards", + "type": "boolean", + "default": false + }, + "targetBlank": { + "description": "If true, the link will be opened in a new tab", + "type": "boolean", + "default": false + }, + "includeVars": { + "description": "If true, includes current template variables values in the link as query params", + "type": "boolean", + "default": false + }, + "keepTime": { + "description": "If true, includes current time range in the link as query params", + "type": "boolean", + "default": false + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DashboardLinkType": { + "description": "Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)", + "type": "string", + "enum": [ + "link", + "dashboards" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DataSourceRef": { + "description": "Ref to a DataSource instance", + "type": "object", + "properties": { + "type": { + "description": "The plugin type-id", + "type": "string" + }, + "uid": { + "description": "Specific datasource instance", + "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DataTransformerConfig": { + "description": "Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.", + "type": "object", + "required": [ + "id", + "options" + ], + "properties": { + "id": { + "description": "Unique identifier of transformer", + "type": "string" + }, + "disabled": { + "description": "Disabled transformations are skipped", + "type": "boolean" + }, + "filter": { + "$ref": "#/components/schemas/MatcherConfig" + }, + "options": { + "description": "Options to be passed to the transformer\nValid options depend on the transformer id" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "DynamicConfigValue": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "default": "" + }, + "value": {} + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "FieldColor": { + "description": "Map a field to a color.", + "type": "object", + "required": [ + "mode" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/FieldColorModeId" + }, + "fixedColor": { + "description": "The fixed color value for fixed or shades color modes.", + "type": "string" + }, + "seriesBy": { + "$ref": "#/components/schemas/FieldColorSeriesByMode" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "FieldColorModeId": { + "description": "Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n`thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold\n`palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n`palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n`continuous-GrYlRd`: ontinuous Green-Yellow-Red palette mode\n`continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode\n`continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode\n`continuous-YlRd`: Continuous Yellow-Red palette mode\n`continuous-BlPu`: Continuous Blue-Purple palette mode\n`continuous-YlBl`: Continuous Yellow-Blue palette mode\n`continuous-blues`: Continuous Blue palette mode\n`continuous-reds`: Continuous Red palette mode\n`continuous-greens`: Continuous Green palette mode\n`continuous-purples`: Continuous Purple palette mode\n`shades`: Shades of a single color. Specify a single color, useful in an override rule.\n`fixed`: Fixed color mode. Specify a single color, useful in an override rule.", + "type": "string", + "enum": [ + "thresholds", + "palette-classic", + "palette-classic-by-name", + "continuous-GrYlRd", + "continuous-RdYlGr", + "continuous-BlYlRd", + "continuous-YlRd", + "continuous-BlPu", + "continuous-YlBl", + "continuous-blues", + "continuous-reds", + "continuous-greens", + "continuous-purples", + "fixed", + "shades" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "FieldColorSeriesByMode": { + "description": "Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.", + "type": "string", + "enum": [ + "min", + "max", + "last" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "FieldConfig": { + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", + "type": "object", + "properties": { + "displayName": { + "description": "The display value for this field. This supports template variables blank is auto", + "type": "string" + }, + "displayNameFromDS": { + "description": "This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.", + "type": "string" + }, + "description": { + "description": "Human readable field metadata", + "type": "string" + }, + "path": { + "description": "An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results", + "type": "string" + }, + "writeable": { + "description": "True if data source can write a value to the path. Auth/authz are supported separately", + "type": "boolean" + }, + "filterable": { + "description": "True if data source field supports ad-hoc filters", + "type": "boolean" + }, + "unit": { + "description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:\u003csuffix\u003e` for custom unit that should go after value.\n`prefix:\u003cprefix\u003e` for custom unit that should go before value.\n`time:\u003cformat\u003e` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:\u003cbase scale\u003e\u003cunit characters\u003e` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:\u003cunit\u003e` for a custom count unit.\n`currency:\u003cunit\u003e` for custom a currency unit.", + "type": "string" + }, + "decimals": { + "description": "Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to `String`.", + "type": "number" + }, + "min": { + "description": "The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", + "type": "number" + }, + "max": { + "description": "The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", + "type": "number" + }, + "mappings": { + "description": "Convert input values into a display string", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValueMapping" + } + }, + "thresholds": { + "$ref": "#/components/schemas/ThresholdsConfig" + }, + "color": { + "$ref": "#/components/schemas/FieldColor" + }, + "links": { + "description": "The behavior when clicking on a result", + "type": "array", + "items": {} + }, + "noValue": { + "description": "Alternative to empty string", + "type": "string" + }, + "custom": { + "description": "custom is specified by the FieldConfig field\nin panel plugin schemas.", + "type": "object" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "FieldConfigSource": { + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", + "type": "object", + "required": [ + "defaults", + "overrides" + ], + "properties": { + "defaults": { + "$ref": "#/components/schemas/FieldConfig" + }, + "overrides": { + "description": "Overrides are the options applied to specific fields overriding the defaults.", + "type": "array", + "items": { + "type": "object", + "required": [ + "matcher", + "properties" + ], + "properties": { + "matcher": { + "$ref": "#/components/schemas/MatcherConfig" + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DynamicConfigValue" + } + } + } + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "GraphPanel": { + "description": "Support for legacy graph panel.\n@deprecated this a deprecated panel type", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "graph" + ] + }, + "legend": { + "description": "@deprecated this is part of deprecated graph panel", + "type": "object", + "required": [ + "show" + ], + "properties": { + "show": { + "type": "boolean", + "default": true + }, + "sort": { + "type": "string" + }, + "sortDesc": { + "type": "boolean" + } + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "GridPos": { + "description": "Position and dimensions of a panel in the grid", + "type": "object", + "required": [ + "h", + "w", + "x", + "y" + ], + "properties": { + "h": { + "description": "Panel height. The height is the number of rows from the top edge of the panel.", + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 4294967295, + "default": 9 + }, + "w": { + "description": "Panel width. The width is the number of columns from the left edge of the panel.", + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 24, + "default": 12 + }, + "x": { + "description": "Panel x. The x coordinate is the number of columns from the left edge of the grid", + "type": "integer", + "minimum": 0, + "maximum": 24, + "exclusiveMaximum": true, + "default": 0 + }, + "y": { + "description": "Panel y. The y coordinate is the number of rows from the top edge of the grid", + "type": "integer", + "minimum": 0, + "maximum": 4294967295, + "default": 0 + }, + "static": { + "description": "Whether the panel is fixed within the grid. If true, the panel will not be affected by other panels' interactions", + "type": "boolean" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "HeatmapPanel": { + "description": "Support for legacy heatmap panel.\n@deprecated this a deprecated panel type", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "heatmap" + ] + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "LibraryPanelRef": { + "description": "A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.", + "type": "object", + "required": [ + "name", + "uid" + ], + "properties": { + "name": { + "description": "Library panel name", + "type": "string" + }, + "uid": { + "description": "Library panel uid", + "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "MappingType": { + "description": "Supported value mapping types\n`value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n`range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n`regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n`special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.", + "type": "string", + "enum": [ + "value", + "range", + "regex", + "special" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "MatcherConfig": { + "description": "Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "The matcher id. This is used to find the matcher implementation from registry.", + "type": "string", + "default": "" + }, + "options": { + "description": "The matcher options. This is specific to the matcher implementation." + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "Panel": { + "description": "Dashboard panels are the basic visualization building blocks.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "The panel plugin type id. This is used to find the plugin to display the panel.", + "type": "string", + "minLength": 1 + }, + "id": { + "description": "Unique identifier of the panel. Generated by Grafana when creating a new panel. It must be unique within a dashboard, but not globally.", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "pluginVersion": { + "description": "The version of the plugin that is used for this panel. This is used to find the plugin to display the panel and to migrate old panel configs.", + "type": "string" + }, + "tags": { + "description": "Tags for the panel.", + "type": "array", + "items": { + "type": "string" + } + }, + "targets": { + "description": "Depends on the panel plugin. See the plugin documentation for details.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Target" + } + }, + "title": { + "description": "Panel title.", + "type": "string" + }, + "description": { + "description": "Panel description.", + "type": "string" + }, + "transparent": { + "description": "Whether to display the panel without a background.", + "type": "boolean", + "default": false + }, + "datasource": { + "$ref": "#/components/schemas/DataSourceRef" + }, + "gridPos": { + "$ref": "#/components/schemas/GridPos" + }, + "links": { + "description": "Panel links.", + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardLink" + } + }, + "repeat": { + "description": "Name of template variable to repeat for.", + "type": "string" + }, + "repeatDirection": { + "description": "Direction to repeat in if 'repeat' is set.\n`h` for horizontal, `v` for vertical.", + "type": "string", + "enum": [ + "h", + "v" + ], + "default": "h" + }, + "maxPerRow": { + "description": "Option for repeated panels that controls max items per row\nOnly relevant for horizontally repeated panels", + "type": "number" + }, + "maxDataPoints": { + "description": "The maximum number of data points that the panel queries are retrieving.", + "type": "number" + }, + "transformations": { + "description": "List of transformations that are applied to the panel data before rendering.\nWhen there are multiple transformations, Grafana applies them in the order they are listed.\nEach transformation creates a result set that then passes on to the next transformation in the processing pipeline.", + "type": "array", + "items": { + "$ref": "#/components/schemas/DataTransformerConfig" + } + }, + "interval": { + "description": "The min time interval setting defines a lower limit for the $__interval and $__interval_ms variables.\nThis value must be formatted as a number followed by a valid time\nidentifier like: \"40s\", \"3d\", etc.\nSee: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options", + "type": "string" + }, + "timeFrom": { + "description": "Overrides the relative time range for individual panels,\nwhich causes them to be different than what is selected in\nthe dashboard time picker in the top-right corner of the dashboard. You can use this to show metrics from different\ntime periods or days on the same dashboard.\nThe value is formatted as time operation like: `now-5m` (Last 5 minutes), `now/d` (the day so far),\n`now-5d/d`(Last 5 days), `now/w` (This week so far), `now-2y/y` (Last 2 years).\nNote: Panel time overrides have no effect when the dashboard’s time range is absolute.\nSee: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options", + "type": "string" + }, + "timeShift": { + "description": "Overrides the time range for individual panels by shifting its start and end relative to the time picker.\nFor example, you can shift the time range for the panel to be two hours earlier than the dashboard time picker setting `2h`.\nNote: Panel time overrides have no effect when the dashboard’s time range is absolute.\nSee: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options", + "type": "string" + }, + "hideTimeOverride": { + "description": "Controls if the timeFrom or timeShift overrides are shown in the panel header", + "type": "boolean" + }, + "libraryPanel": { + "$ref": "#/components/schemas/LibraryPanelRef" + }, + "options": { + "description": "It depends on the panel plugin. They are specified by the Options field in panel plugin schemas.", + "type": "object" + }, + "fieldConfig": { + "$ref": "#/components/schemas/FieldConfigSource" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "RangeMap": { + "description": "Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "type": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/MappingType" + }, + { + "enum": [ + "range" + ] + } + ] + }, + "options": { + "description": "Range to match against and the result to apply when the value is within the range", + "type": "object", + "required": [ + "from", + "to", + "result" + ], + "properties": { + "from": { + "description": "Min value of the range. It can be null which means -Infinity", + "type": "number", + "minimum": -1.797693134862315708145274237317043567981E+308, + "maximum": 1.797693134862315708145274237317043567981E+308 + }, + "to": { + "description": "Max value of the range. It can be null which means +Infinity", + "type": "number", + "minimum": -1.797693134862315708145274237317043567981E+308, + "maximum": 1.797693134862315708145274237317043567981E+308 + }, + "result": { + "$ref": "#/components/schemas/ValueMappingResult" + } + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "RegexMap": { + "description": "Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "type": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/MappingType" + }, + { + "enum": [ + "regex" + ] + } + ] + }, + "options": { + "description": "Regular expression to match against and the result to apply when the value matches the regex", + "type": "object", + "required": [ + "pattern", + "result" + ], + "properties": { + "pattern": { + "description": "Regular expression to match against", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/ValueMappingResult" + } + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "RowPanel": { + "description": "Row panel", + "type": "object", + "required": [ + "type", + "collapsed", + "id", + "panels" + ], + "properties": { + "type": { + "description": "The panel type", + "type": "string", + "enum": [ + "row" + ] + }, + "collapsed": { + "description": "Whether this row should be collapsed or not.", + "type": "boolean", + "default": false + }, + "title": { + "description": "Row title", + "type": "string" + }, + "datasource": { + "$ref": "#/components/schemas/DataSourceRef" + }, + "gridPos": { + "$ref": "#/components/schemas/GridPos" + }, + "id": { + "description": "Unique identifier of the panel. Generated by Grafana when creating a new panel. It must be unique within a dashboard, but not globally.", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "panels": { + "description": "List of panels in the row", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/Panel" + }, + { + "$ref": "#/components/schemas/GraphPanel" + }, + { + "$ref": "#/components/schemas/HeatmapPanel" + } + ] + } + }, + "repeat": { + "description": "Name of template variable to repeat for.", + "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "Snapshot": { + "description": "A dashboard snapshot shares an interactive dashboard publicly.\nIt is a read-only version of a dashboard, and is not editable.\nIt is possible to create a snapshot of a snapshot.\nGrafana strips away all sensitive information from the dashboard.\nSensitive information stripped: queries (metric, template,annotation) and panel links.", + "type": "object", + "required": [ + "created", + "expires", + "external", + "externalUrl", + "id", + "key", + "name", + "orgId", + "updated", + "userId" + ], + "properties": { + "created": { + "description": "Time when the snapshot was created", + "type": "string", + "format": "date-time" + }, + "expires": { + "description": "Time when the snapshot expires, default is never to expire", + "type": "string" + }, + "external": { + "description": "Is the snapshot saved in an external grafana instance", + "type": "boolean" + }, + "externalUrl": { + "description": "external url, if snapshot was shared in external grafana instance", + "type": "string" + }, + "id": { + "description": "Unique identifier of the snapshot", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "key": { + "description": "Optional, defined the unique key of the snapshot, required if external is true", + "type": "string" + }, + "name": { + "description": "Optional, name of the snapshot", + "type": "string" + }, + "orgId": { + "description": "org id of the snapshot", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "updated": { + "description": "last time when the snapshot was updated", + "type": "string", + "format": "date-time" + }, + "url": { + "description": "url of the snapshot, if snapshot was shared internally", + "type": "string" + }, + "userId": { + "description": "user id of the snapshot creator", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "SpecialValueMap": { + "description": "Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "type": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/MappingType" + }, + { + "enum": [ + "special" + ] + } + ] + }, + "options": { + "type": "object", + "required": [ + "match", + "result" + ], + "properties": { + "match": { + "$ref": "#/components/schemas/SpecialValueMatch" + }, + "result": { + "$ref": "#/components/schemas/ValueMappingResult" + } + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "SpecialValueMatch": { + "description": "Special value types supported by the `SpecialValueMap`", + "type": "string", + "enum": [ + "true", + "false", + "null", + "nan", + "null+nan", + "empty" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "Target": { + "description": "Schema for panel targets is specified by datasource\nplugins. We use a placeholder definition, which the Go\nschema loader either left open/as-is with the Base\nvariant of the Dashboard and Panel families, or filled\nwith types derived from plugins in the Instance variant.\nWhen working directly from CUE, importers can extend this\ntype directly to achieve the same effect.", + "type": "object", + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "Threshold": { + "description": "User-defined value for a metric that triggers visual changes in a panel when this value is met or exceeded\nThey are used to conditionally style and color visualizations based on query results , and can be applied to most visualizations.", + "type": "object", + "required": [ + "value", + "color" + ], + "properties": { + "value": { + "description": "Value represents a specified metric for the threshold, which triggers a visual change in the dashboard when this value is met or exceeded.\nNulls currently appear here when serializing -Infinity to JSON.", + "type": "number" + }, + "color": { + "description": "Color represents the color of the visual change that will occur in the dashboard when the threshold value is met or exceeded.", + "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "ThresholdsConfig": { + "description": "Thresholds configuration for the panel", + "type": "object", + "required": [ + "mode", + "steps" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/ThresholdsMode" + }, + "steps": { + "description": "Must be sorted by 'value', first value is always -Infinity", + "type": "array", + "items": { + "$ref": "#/components/schemas/Threshold" + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "ThresholdsMode": { + "description": "Thresholds can either be `absolute` (specific number) or `percentage` (relative to min or max, it will be values between 0 and 1).", + "type": "string", + "enum": [ + "absolute", + "percentage" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "ValueMap": { + "description": "Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "type": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/MappingType" + }, + { + "enum": [ + "value" + ] + } + ] + }, + "options": { + "description": "Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ValueMappingResult" + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "ValueMapping": { + "description": "Allow to transform the visual representation of specific data values in a visualization, irrespective of their original units", + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/ValueMap" + }, + { + "$ref": "#/components/schemas/RangeMap" + }, + { + "$ref": "#/components/schemas/RegexMap" + }, + { + "$ref": "#/components/schemas/SpecialValueMap" + } + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "ValueMappingResult": { + "description": "Result used as replacement with text and color when the value matches", + "type": "object", + "properties": { + "text": { + "description": "Text to display when the value matches", + "type": "string" + }, + "color": { + "description": "Text to use when the value matches", + "type": "string" + }, + "icon": { + "description": "Icon to display when the value matches. Only specific visualizations.", + "type": "string" + }, + "index": { + "description": "Position in the mapping array. Only used internally.", + "type": "integer", + "format": "int32" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableHide": { + "description": "Determine if the variable shows on dashboard\nAccepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).", + "type": "integer", + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableModel": { + "description": "A variable is a placeholder for a value. You can use variables in metric queries and in panel titles.", + "type": "object", + "required": [ + "type", + "name" + ], + "properties": { + "type": { + "$ref": "#/components/schemas/VariableType" + }, + "name": { + "description": "Name of variable", + "type": "string" + }, + "label": { + "description": "Optional display name", + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/VariableHide" + }, + "skipUrlSync": { + "description": "Whether the variable value should be managed by URL query params or not", + "type": "boolean", + "default": false + }, + "description": { + "description": "Description of variable. It can be defined but `null`.", + "type": "string" + }, + "query": { + "description": "Query used to fetch values for a variable", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "datasource": { + "$ref": "#/components/schemas/DataSourceRef" + }, + "current": { + "$ref": "#/components/schemas/VariableOption" + }, + "multi": { + "description": "Whether multiple values can be selected or not from variable value list", + "type": "boolean", + "default": false + }, + "options": { + "description": "Options that can be selected for a variable.", + "type": "array", + "items": { + "$ref": "#/components/schemas/VariableOption" + } + }, + "refresh": { + "$ref": "#/components/schemas/VariableRefresh" + }, + "sort": { + "$ref": "#/components/schemas/VariableSort" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableOption": { + "description": "Option to be selected in a variable.", + "type": "object", + "required": [ + "text", + "value" + ], + "properties": { + "selected": { + "description": "Whether the option is selected or not", + "type": "boolean" + }, + "text": { + "description": "Text to be displayed for the option", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "value": { + "description": "Value of the option", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableRefresh": { + "description": "Options to config when to refresh a variable\n`0`: Never refresh the variable\n`1`: Queries the data source every time the dashboard loads.\n`2`: Queries the data source when the dashboard time range changes.", + "type": "integer", + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableSort": { + "description": "Sort variable options\nAccepted values are:\n`0`: No sorting\n`1`: Alphabetical ASC\n`2`: Alphabetical DESC\n`3`: Numerical ASC\n`4`: Numerical DESC\n`5`: Alphabetical Case Insensitive ASC\n`6`: Alphabetical Case Insensitive DESC", + "type": "integer", + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "VariableType": { + "description": "Dashboard variable type\n`query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.\n`adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).\n`constant`: \tDefine a hidden constant.\n`datasource`: Quickly change the data source for an entire dashboard.\n`interval`: Interval variables represent time spans.\n`textbox`: Display a free text input field with an optional default value.\n`custom`: Define the variable options manually using a comma-separated list.\n`system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables", + "type": "string", + "enum": [ + "query", + "adhoc", + "constant", + "datasource", + "interval", + "textbox", + "custom", + "system" + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "_kubeObjectMetadata": { + "description": "_kubeObjectMetadata is metadata found in a kubernetes object's metadata field.\nIt is not exhaustive and only includes fields which may be relevant to a kind's implementation,\nAs it is also intended to be generic enough to function with any API Server.", + "type": "object", + "required": [ + "uid", + "creationTimestamp", + "finalizers", + "resourceVersion", + "labels" + ], + "properties": { + "uid": { + "type": "string" + }, + "creationTimestamp": { + "type": "string", + "format": "date-time" + }, + "deletionTimestamp": { + "type": "string", + "format": "date-time" + }, + "finalizers": { + "type": "array", + "items": { + "type": "string" + } + }, + "resourceVersion": { + "type": "string" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "dashboard": { + "type": "object", + "properties": { + "metadata": { + "description": "metadata contains embedded CommonMetadata and can be extended with custom string fields\nTODO: use CommonMetadata instead of redefining here; currently needs to be defined here\nwithout external reference as using the CommonMetadata reference breaks thema codegen.", + "type": "object", + "properties": { + "updateTimestamp": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "updatedBy": { + "type": "string" + }, + "extraFields": { + "description": "extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata", + "type": "object" + } + }, + "allOf": [ + { + "$ref": "#/components/schemas/_kubeObjectMetadata" + }, + { + "required": [ + "updateTimestamp", + "createdBy", + "updatedBy", + "extraFields" + ] + } + ] + }, + "spec": { + "type": "object", + "required": [ + "schemaVersion" + ], + "properties": { + "id": { + "description": "Unique numeric identifier for the dashboard.\n`id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances.", + "type": "integer", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + "uid": { + "description": "Unique dashboard identifier that can be generated by anyone. string (8-40)", + "type": "string" + }, + "title": { + "description": "Title of dashboard.", + "type": "string" + }, + "description": { + "description": "Description of dashboard.", + "type": "string" + }, + "revision": { + "description": "This property should only be used in dashboards defined by plugins. It is a quick check\nto see if the version has changed since the last time.", + "type": "integer", + "format": "int64" + }, + "gnetId": { + "description": "ID of a dashboard imported from the https://grafana.com/grafana/dashboards/ portal", + "type": "string" + }, + "tags": { + "description": "Tags associated with dashboard.", + "type": "array", + "items": { + "type": "string" + } + }, + "timezone": { + "description": "Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".", + "type": "string", + "default": "browser" + }, + "editable": { + "description": "Whether a dashboard is editable or not.", + "type": "boolean", + "default": true + }, + "graphTooltip": { + "$ref": "#/components/schemas/DashboardCursorSync" + }, + "time": { + "description": "Time range for dashboard.\nAccepted values are relative time strings like {from: 'now-6h', to: 'now'} or absolute time strings like {from: '2020-07-10T08:00:00.000Z', to: '2020-07-10T14:00:00.000Z'}.", + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "default": "now-6h" + }, + "to": { + "type": "string", + "default": "now" + } + } + }, + "timepicker": { + "description": "Configuration of the time picker shown at the top of a dashboard.", + "type": "object", + "required": [ + "hidden", + "refresh_intervals", + "collapse", + "time_options" + ], + "properties": { + "hidden": { + "description": "Whether timepicker is visible or not.", + "type": "boolean", + "default": false + }, + "refresh_intervals": { + "description": "Interval options available in the refresh picker dropdown.", + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "collapse": { + "description": "Whether timepicker is collapsed or not. Has no effect on provisioned dashboard.", + "type": "boolean", + "default": false + }, + "time_options": { + "description": "Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.", + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + } + } + }, + "fiscalYearStartMonth": { + "description": "The month that the fiscal year starts on. 0 = January, 11 = December", + "type": "integer", + "minimum": 0, + "maximum": 12, + "exclusiveMaximum": true, + "default": 0 + }, + "liveNow": { + "description": "When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data", + "type": "boolean" + }, + "weekStart": { + "description": "Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".", + "type": "string" + }, + "refresh": { + "description": "Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "schemaVersion": { + "description": "Version of the JSON schema, incremented each time a Grafana update brings\nchanges to said schema.", + "type": "integer", + "minimum": 0, + "maximum": 65535, + "default": 36 + }, + "version": { + "description": "Version of the dashboard, incremented each time the dashboard is updated.", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "panels": { + "description": "List of dashboard panels", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/Panel" + }, + { + "not": { + "anyOf": [ + { + "$ref": "#/components/schemas/RowPanel" + } + ] + } + } + ] + }, + { + "$ref": "#/components/schemas/RowPanel" + }, + { + "$ref": "#/components/schemas/GraphPanel" + }, + { + "$ref": "#/components/schemas/HeatmapPanel" + } + ] + } + }, + "templating": { + "description": "Configured template variables", + "type": "object", + "properties": { + "list": { + "description": "List of configured template variables with their saved values along with some other metadata", + "type": "array", + "items": { + "$ref": "#/components/schemas/VariableModel" + } + } + } + }, + "annotations": { + "$ref": "#/components/schemas/AnnotationContainer" + }, + "links": { + "description": "Links with references to other dashboards or external websites.", + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardLink" + } + }, + "snapshot": { + "$ref": "#/components/schemas/Snapshot" + } + } + }, + "status": { + "type": "object", + "properties": { + "operatorStates": { + "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/status.#OperatorState" + } + }, + "additionalFields": { + "description": "additionalFields is reserved for future use", + "type": "object" + } + } + } + }, + "allOf": [ + { + "required": [ + "metadata", + "spec", + "status" + ] + }, + { + "required": [ + "spec" + ] + } + ], + "$schema": "http://json-schema.org/draft-04/schema#" + }, + "status.#OperatorState": { + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string" + }, + "state": { + "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", + "type": "string", + "enum": [ + "success", + "in_progress", + "failed" + ] + }, + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object" + } + }, + "$schema": "http://json-schema.org/draft-04/schema#" + } + } + } +} From 872499086082ad9bcb358060c492139de968d6c6 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Tue, 9 Jan 2024 09:32:12 +0000 Subject: [PATCH 8/9] Clean-up allOf generator logic, remove not from the dashboardSpecPanels type test --- lib/generator.ml | 31 +++++++++++++++------------- tests/grok.t | 2 +- tests/mocks/dashboard_types_gen.json | 15 +------------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index 7e7153f..b41598a 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -26,21 +26,18 @@ let get_ref_name (ref : ref_) = let toplevel_definitions = Buffer.create 16 let global_defs = ref [] -let schemas_to_obj = List.map (fun (name, schema) -> name, Obj schema) -let rec get_ref_schema ~schema ref = +let get_schema_by_ref ~schema ref = let defs = match schema.defs with | None -> !global_defs - | Some defs -> schemas_to_obj defs @ !global_defs + | Some defs -> defs @ !global_defs in List.find_map - (fun (name, schema_or_ref) -> - match schema_or_ref with - | Obj schema when String.equal (get_ref_name ref) name -> Some schema - | Obj _ -> None - | Ref ref -> get_ref_schema ~schema ref - ) + (function + | name, schema when String.equal (get_ref_name ref) name -> Some schema + | _ -> None + ) defs let rec ocaml_value_of_json = function @@ -69,7 +66,7 @@ let merge_all_of schema = List.filter_map (function | Obj schema -> Some schema - | Ref ref -> get_ref_schema ~schema ref + | Ref ref -> get_schema_by_ref ~schema ref ) all_of in @@ -82,7 +79,6 @@ let merge_all_of schema = let merge_lists get_fn = schemas |> List.map get_fn |> List.flatten in let merge_opt_lists get_fn = schemas |> List.filter_map get_fn |> List.flatten |> Utils.nonempty_list_opt in { - schema with schema = take_first_opt (fun schema -> schema.schema); all_of = merge_opt_lists (fun schema -> schema.all_of); any_of = merge_opt_lists (fun schema -> schema.any_of); @@ -112,6 +108,7 @@ let merge_all_of schema = format = take_first_opt (fun schema -> schema.format); defs = merge_opt_lists (fun schema -> schema.defs); title = take_first_opt (fun schema -> schema.title); + typ = take_first_opt (fun schema -> schema.typ); description = take_first_opt (fun schema -> schema.description); default = take_first_opt (fun schema -> schema.default); nullable = schemas |> List.exists (fun schema -> schema.nullable); @@ -177,7 +174,7 @@ and process_one_of ~ancestors (schemas_or_refs : schema or_ref list) = let determine_variant_name = function | Ref ref_ -> variant_name (get_ref_name ref_) | Obj schema -> - match schema.typ with + match (merge_all_of schema).typ with | Some Array -> concat_camelCase (process_array_type ~ancestors schema) | Some Object -> "Json" | _ -> variant_name (process_schema_type ~ancestors schema) @@ -214,7 +211,7 @@ let make_atd_of_jsonschema input = let schema = Json_schema_j.schema_of_string input in let root_type_name = Option.value ~default:"root" schema.title in Buffer.clear toplevel_definitions; - Option.iter (fun defs -> global_defs := schemas_to_obj defs) schema.defs; + Option.iter (fun defs -> global_defs := defs) schema.defs; base ^ "\n" ^ process_schemas [ root_type_name, Obj schema ] let make_atd_of_openapi input = @@ -225,6 +222,12 @@ let make_atd_of_openapi input = match components.schemas with | Some schemas -> Buffer.clear toplevel_definitions; - global_defs := schemas; + global_defs := + List.filter_map + (function + | _name, Ref _ -> None + | name, Obj schema -> Some (name, schema) + ) + schemas; base ^ "\n" ^ process_schemas schemas | None -> failwith "components schemas are empty" diff --git a/tests/grok.t b/tests/grok.t index ceccb09..d6bfbee 100644 --- a/tests/grok.t +++ b/tests/grok.t @@ -116,7 +116,7 @@ Generate ATD types from grok (Grafana Object Development Kit) dashboard types ] type dashboardSpecPanels = [ - | Json of json + | Panel of panel | RowPanel of rowPanel | GraphPanel of graphPanel | HeatmapPanel of heatmapPanel diff --git a/tests/mocks/dashboard_types_gen.json b/tests/mocks/dashboard_types_gen.json index a630fd2..9dce39f 100644 --- a/tests/mocks/dashboard_types_gen.json +++ b/tests/mocks/dashboard_types_gen.json @@ -1487,20 +1487,7 @@ "type": "object", "oneOf": [ { - "allOf": [ - { - "$ref": "#/components/schemas/Panel" - }, - { - "not": { - "anyOf": [ - { - "$ref": "#/components/schemas/RowPanel" - } - ] - } - } - ] + "$ref": "#/components/schemas/Panel" }, { "$ref": "#/components/schemas/RowPanel" From 110d93fab99358b208cb9fba475e017f08155417 Mon Sep 17 00:00:00 2001 From: Egor Chemokhonenko Date: Tue, 9 Jan 2024 13:45:44 +0000 Subject: [PATCH 9/9] ATD builder logic refactoring Fix Fix --- lib/generator.ml | 62 ++++++++++++++++++++++++------------------------ tests/base.ml | 2 +- tests/grok.t | 5 +--- tests/smoke.t | 15 ++++-------- 4 files changed, 38 insertions(+), 46 deletions(-) diff --git a/lib/generator.ml b/lib/generator.ml index b41598a..4f6a692 100644 --- a/lib/generator.ml +++ b/lib/generator.ml @@ -6,7 +6,7 @@ let record_field_name str = let cleaned_field_name = Utils.sanitize_name str in if String.equal str cleaned_field_name then str else sprintf {|%s |} cleaned_field_name str -let define_top_level name type_ = sprintf "type %s = %s\n\n" (type_name name) type_ +let define_type name type_ = sprintf "type %s = %s\n" (type_name name) type_ let process_int_type schema = match schema.format with @@ -14,7 +14,7 @@ let process_int_type schema = | Some `Int64 -> "int64" | _ -> failwith "int has unextected format" -let get_ref_name (ref : ref_) = +let get_ref_name ref = match String.split_on_char '/' ref with (* OpenAPI defs *) | [ "#"; "components"; "schemas"; type_name ] -> type_name @@ -24,14 +24,14 @@ let get_ref_name (ref : ref_) = failwith (Printf.sprintf "Unsupported ref value: %s. Supported ref URI are: #/components/schemas/* and #/$defs/*" ref) -let toplevel_definitions = Buffer.create 16 -let global_defs = ref [] +let output = Buffer.create 16 +let input_toplevel_schemas = ref [] let get_schema_by_ref ~schema ref = let defs = match schema.defs with - | None -> !global_defs - | Some defs -> defs @ !global_defs + | None -> !input_toplevel_schemas + | Some defs -> defs @ !input_toplevel_schemas in List.find_map (function @@ -144,8 +144,8 @@ and process_nested_schema_type ~ancestors schema = match merge_all_of schema with | { one_of = Some _; _ } | { typ = Some Object; properties = Some _; _ } | { enum = Some _; _ } -> let nested_type_name = concat_camelCase (List.rev ancestors) in - let nested = define_top_level nested_type_name (process_schema_type ~ancestors schema) in - Buffer.add_string toplevel_definitions nested; + let nested = define_type nested_type_name (process_schema_type ~ancestors schema) in + Buffer.add_string output (nested ^ "\n"); type_name nested_type_name | _ -> process_schema_type ~ancestors schema @@ -193,26 +193,35 @@ and process_enums enums = sprintf "[\n%s\n]" variants let process_schemas (schemas : (string * schema or_ref) list) = - let atd_schemas = - List.fold_left - (fun acc (name, schema_or_ref) -> - define_top_level name (make_type_from_schema_or_ref ~ancestors:[ name ] schema_or_ref) :: acc - ) - [] schemas - in - String.concat "" (Buffer.contents toplevel_definitions :: atd_schemas) - -let base = {| + List.fold_left + (fun acc (name, schema_or_ref) -> + define_type name (make_type_from_schema_or_ref ~ancestors:[ name ] schema_or_ref) :: acc + ) + [] schemas + +let base = + {|(* Generated by jsonschema2atd *) type json = abstract type int64 = int |} +let make_atd_of_schemas schemas = + input_toplevel_schemas := + List.filter_map + (function + | _name, Ref _ -> None + | name, Obj schema -> Some (name, schema) + ) + schemas; + Buffer.clear output; + Buffer.add_string output (base ^ "\n"); + Buffer.add_string output (String.concat "\n" (process_schemas schemas)); + Buffer.contents output + let make_atd_of_jsonschema input = let schema = Json_schema_j.schema_of_string input in let root_type_name = Option.value ~default:"root" schema.title in - Buffer.clear toplevel_definitions; - Option.iter (fun defs -> global_defs := defs) schema.defs; - base ^ "\n" ^ process_schemas [ root_type_name, Obj schema ] + make_atd_of_schemas [ root_type_name, Obj schema ] let make_atd_of_openapi input = let root = Openapi_j.root_of_string input in @@ -220,14 +229,5 @@ let make_atd_of_openapi input = | None -> failwith "components are empty" | Some components -> match components.schemas with - | Some schemas -> - Buffer.clear toplevel_definitions; - global_defs := - List.filter_map - (function - | _name, Ref _ -> None - | name, Obj schema -> Some (name, schema) - ) - schemas; - base ^ "\n" ^ process_schemas schemas + | Some schemas -> make_atd_of_schemas schemas | None -> failwith "components schemas are empty" diff --git a/tests/base.ml b/tests/base.ml index 25d7d8d..0e52734 100644 --- a/tests/base.ml +++ b/tests/base.ml @@ -6,7 +6,7 @@ let openapi_json_template schemas = schemas let replace_whitespace str = Str.global_replace (Str.regexp "[ \t\n\r]+") "" str -let remove_prelude str = Str.global_replace (Str.regexp Generator.base) "" str +let remove_prelude str = Str.global_replace (Str.regexp (Str.quote Generator.base)) "" str let test_strings_cmp a b = String.equal (replace_whitespace a) (replace_whitespace b) let assert_schema input output = diff --git a/tests/grok.t b/tests/grok.t index d6bfbee..d4b4ccf 100644 --- a/tests/grok.t +++ b/tests/grok.t @@ -1,6 +1,6 @@ Generate ATD types from grok (Grafana Object Development Kit) dashboard types $ jsonschema2atd --format openapi ./mocks/dashboard_types_gen.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int @@ -487,6 +487,3 @@ Generate ATD types from grok (Grafana Object Development Kit) dashboard types type annotationContainer = { ?list: annotationQuery list option; } - - - diff --git a/tests/smoke.t b/tests/smoke.t index 6450983..f7eb263 100644 --- a/tests/smoke.t +++ b/tests/smoke.t @@ -1,50 +1,45 @@ Generate ATD out of JSON Scheme $ jsonschema2atd ./mocks/simple_jsonschema.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int type product = { ?productId: int option; } - Generate ATD out of JSON Scheme with --format attribute $ jsonschema2atd --format jsonschema ./mocks/simple_jsonschema.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int type product = { ?productId: int option; } - Generate ATD out of JSON Scheme with -f attribute $ jsonschema2atd -f jsonschema ./mocks/simple_jsonschema.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int type product = { ?productId: int option; } - Generate ATD out of OpenAPI doc with --format attribute $ jsonschema2atd --format openapi ./mocks/simple_openapi.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int type product = { ?productId: int option; } - Generate ATD out of OpenAPI doc with -f attribute $ jsonschema2atd -f openapi ./mocks/simple_openapi.json - + (* Generated by jsonschema2atd *) type json = abstract type int64 = int type product = { ?productId: int option; } -