Skip to content

Commit

Permalink
Switch Metric.Registry to use embedded schemas instead of arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanIvanoff committed Nov 5, 2024
1 parent 0042c15 commit 617c25f
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 78 deletions.
36 changes: 18 additions & 18 deletions lib/sanbase/metric/registry/populate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
122 changes: 92 additions & 30 deletions lib/sanbase/metric/registry/registry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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(),
Expand All @@ -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: %{})

Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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"])
Expand Down Expand Up @@ -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,
Expand All @@ -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
8 changes: 4 additions & 4 deletions lib/sanbase/metric/registry/validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ defmodule SanbaseWeb.MetricRegistryEditLive do
label="Human Readable Name"
/>
<!-- <.input type="text" id="input-table" field={@form[:table]} label="Table" /> -->
<.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"
Expand All @@ -78,6 +77,20 @@ defmodule SanbaseWeb.MetricRegistryEditLive do
/>
<.input type="textarea" id="input-parameters" field={@form[:parameters]} label="Parameters" />
<h3>Tables</h3>
<.inputs_for :let={fp} field={@form[:tables]}>
<.input field={fp[:name]} type="text" label="Table" />
<label class="cursor-pointer">
<input type="checkbox" name="registry[tables_drop][]" value={fp.index} class="hidden" />
<.icon name="hero-x-mark" class="w-6 h-6 relative bg-red-800" /> Remove table
</label>
</.inputs_for>
<label class="block cursor-pointer">
<input type="checkbox" name="registry[tables_add][]" class="hidden" /> Add new table
</label>
</.simple_form>
</div>
"""
Expand Down Expand Up @@ -125,5 +138,6 @@ defmodule SanbaseWeb.MetricRegistryEditLive do
{:ok, %{params | "table" => List.wrap(params["table"])}}
end

defp maybe_update_if_present({:ok, params}, _), do: {:ok, params}
defp maybe_update_if_present({:error, error}, _), do: {:error, error}
end
14 changes: 12 additions & 2 deletions lib/sanbase_web/live/metric_registry/metric_registry_index_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ defmodule SanbaseWeb.MetricRegistryIndexLive do
popover_target="popover-table"
popover_target_text={get_popover_text(%{key: "Clickhouse Table"})}
>
<%= row.table %>
<.embeded_schema_show list={row.tables} key={:name} />
</:col>
<:col
:let={row}
label="Default Aggregation"
popover_target="popover-default-aggregation"
popover_target_text={get_popover_text(%{key: "Default Aggregation"})}
>
<%= row.aggregation %>
<%= row.default_aggregation %>
</:col>
<:col
:let={row}
Expand All @@ -83,6 +83,16 @@ defmodule SanbaseWeb.MetricRegistryIndexLive do
"""
end

def embeded_schema_show(assigns) do
~H"""
<div>
<div :for={item <- @list}>
<%= Map.get(item, @key) %>
</div>
</div>
"""
end

@impl true
def handle_event("apply_filters", params, socket) do
visible_metrics =
Expand Down
Loading

0 comments on commit 617c25f

Please sign in to comment.