diff --git a/lib/sanbase/metric/registry/populate.ex b/lib/sanbase/metric/registry/populate.ex index 6e3b0b33b..4cdefaab5 100644 --- a/lib/sanbase/metric/registry/populate.ex +++ b/lib/sanbase/metric/registry/populate.ex @@ -14,32 +14,32 @@ defmodule Sanbase.Metric.Registry.Populate do def json_map_to_registry_changeset(%{} = map) do {:ok, captures} = Sanbase.TemplateEngine.Captures.extract_captures(map["name"]) - is_template_metric = captures != [] + is_template = captures != [] %Sanbase.Metric.Registry{} |> Sanbase.Metric.Registry.changeset(%{ - metric: map["name"], - internal_metric: map["metric"], - human_readable_name: map["human_readable_name"], - aliases: Map.get(map, "aliases", []), access: map["access"], - min_plan: Map.get(map, "min_plan", %{}), - table: map["table"] |> List.wrap(), - aggregation: map["aggregation"], - selectors: map["selectors"], - required_selectors: map["required_selectors"], - min_interval: map["min_interval"], - is_template_metric: is_template_metric, - parameters: Map.get(map, "parameters", []), + default_aggregation: map["aggregation"], + aliases: Map.get(map, "aliases", []) |> Enum.map(&%{name: &1}), + data_type: map["data_type"], + deprecation_note: map["deprecation_note"], + docs: Map.get(map, "docs_links", []) |> Enum.map(&%{link: &1}), fixed_parameters: Map.get(map, "fixed_parameters", %{}), - is_deprecated: Map.get(map, "is_deprecated", false), hard_deprecate_after: map["hard_deprecate_after"], - deprecation_note: map["deprecation_note"], has_incomplete_data: Map.get(map, "has_incomplete_data", false), - data_type: map["data_type"], - docs_links: Map.get(map, "docs_links", []), + human_readable_name: map["human_readable_name"], + internal_metric: map["metric"], + is_deprecated: Map.get(map, "is_deprecated", false), + is_hidden: Map.get(map, "is_hidden", false), + is_template: is_template, is_timebound: Map.get(map, "is_timebound", false), - is_hidden: Map.get(map, "is_hidden", false) + metric: map["name"], + min_interval: map["min_interval"], + min_plan: Map.get(map, "min_plan", %{}), + parameters: Map.get(map, "parameters", []), + required_selectors: Map.get(map, "required_selectors", []) |> Enum.map(&%{type: &1}), + selectors: Map.get(map, "selectors", []) |> Enum.map(&%{type: &1}), + tables: map["table"] |> List.wrap() |> Enum.map(&%{name: &1}) }) end diff --git a/lib/sanbase/metric/registry/registry.ex b/lib/sanbase/metric/registry/registry.ex index 6d439092b..854b974ef 100644 --- a/lib/sanbase/metric/registry/registry.ex +++ b/lib/sanbase/metric/registry/registry.ex @@ -8,9 +8,9 @@ defmodule Sanbase.Metric.Registry do alias __MODULE__.Validation alias Sanbase.TemplateEngine - # Matches letters, digits, _, -, :, {, }, (, ), \, / and space + # Matches letters, digits, _, -, :, ., {, }, (, ), \, / and space # Careful not to delete the space at the end - @human_readable_name_regex ~r|^[a-zA-Z0-9_-{}():/\\ ]+$| + @human_readable_name_regex ~r|^[a-zA-Z0-9_\.\-{}():/\\ ]+$| @aggregations ["sum", "last", "count", "avg", "max", "min", "first"] def aggregations(), do: @aggregations @@ -20,14 +20,14 @@ defmodule Sanbase.Metric.Registry do human_readable_name: String.t(), aliases: [String.t()], internal_metric: String.t(), - table: [String.t()], - aggregation: String.t(), + tables: [String.t()], + default_aggregation: String.t(), min_interval: String.t(), access: String.t(), min_plan: map(), selectors: [String.t()], required_selectors: [String.t()], - is_template_metric: boolean(), + is_template: boolean(), parameters: [map()], fixed_parameters: map(), is_timebound: boolean(), @@ -39,32 +39,96 @@ defmodule Sanbase.Metric.Registry do hard_deprecate_after: DateTime.t(), deprecation_note: String.t(), data_type: String.t(), - docs_links: [String.t()], + docs: [String.t()], inserted_at: DateTime.t(), updated_at: DateTime.t() } + defmodule Selector do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field(:type, :string) + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:type]) + |> validate_required([:type]) + end + end + + defmodule Table do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field(:name, :string) + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:name]) + |> validate_required([:name]) + end + end + + defmodule Alias do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field(:name, :string) + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:name]) + |> validate_required([:name]) + end + end + + defmodule Doc do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field(:link, :string) + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:link]) + |> validate_required([:link]) + end + end + @timestamps_opts [type: :utc_datetime] schema "metric_registry" do # How the metric is exposed to external users field(:metric, :string) field(:human_readable_name, :string) - field(:aliases, {:array, :string}, default: []) + embeds_many(:aliases, Alias) # What is the name of the metric in the DB and where to find it field(:internal_metric, :string) - field(:table, {:array, :string}) + embeds_many(:tables, Table) - field(:aggregation, :string) + field(:default_aggregation, :string) field(:min_interval, :string) field(:access, :string) field(:min_plan, :map) - field(:selectors, {:array, :string}) - field(:required_selectors, {:array, :string}) + embeds_many(:selectors, Selector) + embeds_many(:required_selectors, Selector) # If the metric is a template metric, then the parameters need to be used # to define the full set of metrics - field(:is_template_metric, :boolean, default: false) + field(:is_template, :boolean, default: false) field(:parameters, {:array, :map}, default: []) field(:fixed_parameters, :map, default: %{}) @@ -80,7 +144,7 @@ defmodule Sanbase.Metric.Registry do field(:deprecation_note, :string, default: nil) field(:data_type, :string, default: "timeseries") - field(:docs_links, {:array, :string}, default: []) + embeds_many(:docs, Doc) timestamps() end @@ -89,11 +153,9 @@ defmodule Sanbase.Metric.Registry do metric_registry |> cast(attrs, [ :access, - :aggregation, - :aliases, + :default_aggregation, :data_type, :deprecation_note, - :docs_links, :exposed_environments, :fixed_parameters, :hard_deprecate_after, @@ -103,34 +165,34 @@ defmodule Sanbase.Metric.Registry do :is_deprecated, :is_exposed, :is_hidden, - :is_template_metric, + :is_template, :is_timebound, :metric, :min_interval, :min_plan, - :parameters, - :required_selectors, - :selectors, - :table + :parameters ]) + |> cast_embed(:selectors, required: false, with: &Selector.changeset/2) + |> cast_embed(:required_selectors, required: false, with: &Selector.changeset/2) + |> cast_embed(:tables, required: false, with: &Table.changeset/2) + |> cast_embed(:aliases, required: false, with: &Alias.changeset/2) |> validate_required([ :access, - :aggregation, + :default_aggregation, :has_incomplete_data, :human_readable_name, :internal_metric, :metric, - :min_interval, - :table + :min_interval ]) - |> validate_format(:metric, ~r/^[a-z0-9_]+$/) - |> validate_format(:internal_metric, ~r/^[a-z0-9_]+$/) + |> validate_format(:metric, ~r/^[a-z0-9_{}:]+$/) + |> validate_format(:internal_metric, ~r/^[a-z0-9_{}:]+$/) # Careful not to delete the space symbol at the end |> validate_format(:human_readable_name, @human_readable_name_regex) |> validate_length(:metric, min: 3, max: 100) |> validate_length(:internal_metric, min: 3, max: 100) - |> validate_length(:human_readable_name, min: 3, max: 100) - |> validate_inclusion(:aggregation, @aggregations) + |> validate_length(:human_readable_name, min: 3, max: 120) + |> validate_inclusion(:default_aggregation, @aggregations) |> validate_inclusion(:data_type, ["timeseries", "histogram", "table"]) |> validate_inclusion(:exposed_environments, ["all", "stage", "prod"]) |> validate_inclusion(:access, ["free", "restricted"]) @@ -201,7 +263,7 @@ defmodule Sanbase.Metric.Registry do end defp apply_template_parameters(%__MODULE__{} = registry) - when registry.is_template_metric == true do + when registry.is_template == true do %{ metric: metric, internal_metric: internal_metric, @@ -219,6 +281,6 @@ defmodule Sanbase.Metric.Registry do end end - defp apply_template_parameters(registry) when registry.is_template_metric == false, + defp apply_template_parameters(registry) when registry.is_template == false, do: [registry] end diff --git a/lib/sanbase/metric/registry/validation.ex b/lib/sanbase/metric/registry/validation.ex index c69a8f830..1b86869b8 100644 --- a/lib/sanbase/metric/registry/validation.ex +++ b/lib/sanbase/metric/registry/validation.ex @@ -45,25 +45,25 @@ defmodule Sanbase.Metric.Registry.Validation do end def validate_template_fields(%Ecto.Changeset{} = changeset) do - is_template_metric = get_field(changeset, :is_template_metric) + is_template = get_field(changeset, :is_template) parameters = get_field(changeset, :parameters) cond do - is_template_metric and parameters == [] -> + is_template and parameters == [] -> add_error( changeset, :parameters, "When the metric is labeled as template metric, parameters cannot be empty" ) - not is_template_metric and parameters != [] -> + not is_template and parameters != [] -> add_error( changeset, :parameters, "When the metric is not labeled as template metric, the parameters must be empty" ) - is_template_metric and parameters != [] -> + is_template and parameters != [] -> validate_parameters_match_captures(changeset) true -> diff --git a/lib/sanbase_web/live/metric_registry/metric_registry_edit_live.ex b/lib/sanbase_web/live/metric_registry/metric_registry_edit_live.ex index 4ab540490..fdcc5cf6c 100644 --- a/lib/sanbase_web/live/metric_registry/metric_registry_edit_live.ex +++ b/lib/sanbase_web/live/metric_registry/metric_registry_edit_live.ex @@ -51,7 +51,6 @@ defmodule SanbaseWeb.MetricRegistryEditLive do label="Human Readable Name" /> - <.array_input id="input-table" field={@field[:table]} label="Table" /> <.input type="text" id="input-min-interval" field={@form[:min_interval]} label="Min Interval" /> <.input type="select" @@ -78,6 +77,20 @@ defmodule SanbaseWeb.MetricRegistryEditLive do /> <.input type="textarea" id="input-parameters" field={@form[:parameters]} label="Parameters" /> + +