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" /> + +

Tables

+ <.inputs_for :let={fp} field={@form[:tables]}> + <.input field={fp[:name]} type="text" label="Table" /> + + + + + """ @@ -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 diff --git a/lib/sanbase_web/live/metric_registry/metric_registry_index_live.ex b/lib/sanbase_web/live/metric_registry/metric_registry_index_live.ex index 0795b3ecb..a6ba2d5bb 100644 --- a/lib/sanbase_web/live/metric_registry/metric_registry_index_live.ex +++ b/lib/sanbase_web/live/metric_registry/metric_registry_index_live.ex @@ -52,7 +52,7 @@ 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 :let={row} @@ -60,7 +60,7 @@ defmodule SanbaseWeb.MetricRegistryIndexLive do popover_target="popover-default-aggregation" popover_target_text={get_popover_text(%{key: "Default Aggregation"})} > - <%= row.aggregation %> + <%= row.default_aggregation %> <:col :let={row} @@ -83,6 +83,16 @@ defmodule SanbaseWeb.MetricRegistryIndexLive do """ end + def embeded_schema_show(assigns) do + ~H""" +
+
+ <%= Map.get(item, @key) %> +
+
+ """ + end + @impl true def handle_event("apply_filters", params, socket) do visible_metrics = diff --git a/lib/sanbase_web/live/metric_registry/metric_registry_show_live.ex b/lib/sanbase_web/live/metric_registry/metric_registry_show_live.ex index e265f41bf..5c399ff82 100644 --- a/lib/sanbase_web/live/metric_registry/metric_registry_show_live.ex +++ b/lib/sanbase_web/live/metric_registry/metric_registry_show_live.ex @@ -32,6 +32,12 @@ defmodule SanbaseWeb.MetricRegistryShowLive do href={~p"/metric_registry"} icon="hero-arrow-uturn-left" /> + + <.table id="metric_registry" rows={@rows}> <:col :let={row} col_class="w-40"> @@ -88,7 +94,7 @@ defmodule SanbaseWeb.MetricRegistryShowLive do }, %{ key: "Table", - value: Enum.join(List.wrap(metric_registry.table), ", "), + value: metric_registry.tables |> Enum.map(& &1.name) |> Enum.join(", "), popover_target: "popover-clickhouse-table", popover_target_text: get_popover_text(%{key: "Clickhouse Table"}) }, @@ -106,7 +112,7 @@ defmodule SanbaseWeb.MetricRegistryShowLive do }, %{ key: "Docs", - value: metric_registry.docs_links, + value: metric_registry.docs |> Enum.map(& &1.link) |> Enum.join(", "), popover_target: "popover-docs", popover_target_text: get_popover_text(%{key: "Docs"}) }, @@ -118,7 +124,7 @@ defmodule SanbaseWeb.MetricRegistryShowLive do }, %{ key: "Default Aggregation", - value: stringify(metric_registry.aggregation), + value: stringify(metric_registry.default_aggregation), popover_target: "popover-default-aggregation", popover_target_text: get_popover_text(%{key: "Default Aggregation"}) }, @@ -136,7 +142,7 @@ defmodule SanbaseWeb.MetricRegistryShowLive do }, %{ key: "Is Template Metric", - value: metric_registry.is_template_metric, + value: metric_registry.is_template, popover_target: "popover-template-metric", popover_target_text: get_popover_text(%{key: "Is Template Metric"}) }, @@ -160,13 +166,14 @@ defmodule SanbaseWeb.MetricRegistryShowLive do }, %{ key: "Selectors", - value: Jason.encode!(metric_registry.selectors), + value: metric_registry.selectors |> Enum.map(&Map.from_struct/1) |> Jason.encode!(), popover_target: "popover-selectors", popover_target_text: get_popover_text(%{key: "Available Selectors"}) }, %{ key: "Required Selectors", - value: Jason.encode!(metric_registry.required_selectors), + value: + metric_registry.required_selectors |> Enum.map(&Map.from_struct/1) |> Jason.encode!(), popover_target: "popover-required-selectors", popover_target_text: get_popover_text(%{key: "Required Selectors"}) }, diff --git a/priv/repo/migrations/20241104115340_create_metric_registry.exs b/priv/repo/migrations/20241104115340_create_metric_registry.exs index fbbde2c6b..8b9f5b569 100644 --- a/priv/repo/migrations/20241104115340_create_metric_registry.exs +++ b/priv/repo/migrations/20241104115340_create_metric_registry.exs @@ -63,11 +63,11 @@ defmodule Sanbase.Repo.Migrations.CreateMetricRegistry do add(:metric, :string, null: false) add(:internal_metric, :string, null: false) add(:human_readable_name, :string, null: false) - add(:aliases, :string, null: false, default: []) - add(:tables, :string, null: false) + add(:aliases, :map) + add(:tables, :map, null: false) add(:is_template, :boolean, null: false, default: false) - add(:parameters, :map, null: false, default: []) + add(:parameters, :map, null: false, default: "{}") add(:fixed_parameters, :map, null: "false", default: "{}") add(:is_timebound, :boolean, null: false, null: false) @@ -75,8 +75,8 @@ defmodule Sanbase.Repo.Migrations.CreateMetricRegistry do add(:exposed_environments, :string, null: false, default: "all") add(:version, :string) - add(:selectors, :map, null: false, default: []) - add(:required_selectors, :map, null: false, default: "{}") + add(:selectors, :map) + add(:required_selectors, :map) add(:access, :string, null: false) add(:min_plan, :jsonb, null: false, default: "{}") @@ -84,7 +84,7 @@ defmodule Sanbase.Repo.Migrations.CreateMetricRegistry do add(:min_interval, :string, null: false) add(:has_incomplete_data, :boolean, null: false) add(:data_type, :string, null: false, default: "timeseries") - add(:docs_links, :map, null: false, default: "{}") + add(:docs, :map) add(:is_hidden, :boolean, null: false, default: false) add(:is_deprecated, :boolean, null: false, default: false) diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index fc7ea8ffe..298ad1e74 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -2285,24 +2285,24 @@ CREATE TABLE public.metric_registry ( metric character varying(255) NOT NULL, internal_metric character varying(255) NOT NULL, human_readable_name character varying(255) NOT NULL, - aliases character varying(255)[] DEFAULT ARRAY[]::character varying[] NOT NULL, - "table" character varying(255)[] NOT NULL, - is_template_metric boolean DEFAULT false NOT NULL, - parameters jsonb[] DEFAULT ARRAY[]::jsonb[] NOT NULL, + aliases jsonb, + tables jsonb NOT NULL, + is_template boolean DEFAULT false NOT NULL, + parameters jsonb DEFAULT '{}'::jsonb NOT NULL, fixed_parameters jsonb DEFAULT '{}'::jsonb, is_timebound boolean NOT NULL, is_exposed boolean DEFAULT true NOT NULL, exposed_environments character varying(255) DEFAULT 'all'::character varying NOT NULL, version character varying(255), - selectors character varying(255)[] DEFAULT ARRAY[]::character varying[] NOT NULL, - required_selectors character varying(255)[] DEFAULT ARRAY[]::character varying[] NOT NULL, + selectors jsonb, + required_selectors jsonb, access character varying(255) NOT NULL, min_plan jsonb DEFAULT '{}'::jsonb NOT NULL, - aggregation character varying(255) NOT NULL, + default_aggregation character varying(255) NOT NULL, min_interval character varying(255) NOT NULL, has_incomplete_data boolean NOT NULL, data_type character varying(255) DEFAULT 'timeseries'::character varying NOT NULL, - docs_links character varying(255)[] DEFAULT ARRAY[]::character varying[] NOT NULL, + docs jsonb, is_hidden boolean DEFAULT false NOT NULL, is_deprecated boolean DEFAULT false NOT NULL, hard_deprecate_after timestamp(0) without time zone DEFAULT NULL::timestamp without time zone, @@ -9610,12 +9610,10 @@ INSERT INTO public."schema_migrations" (version) VALUES (20240809122904); INSERT INTO public."schema_migrations" (version) VALUES (20240904135651); INSERT INTO public."schema_migrations" (version) VALUES (20240926130910); INSERT INTO public."schema_migrations" (version) VALUES (20240926135951); -INSERT INTO public."schema_migrations" (version) VALUES (20241014115340); INSERT INTO public."schema_migrations" (version) VALUES (20241017092520); INSERT INTO public."schema_migrations" (version) VALUES (20241018073651); INSERT INTO public."schema_migrations" (version) VALUES (20241018075640); -INSERT INTO public."schema_migrations" (version) VALUES (20241018115340); INSERT INTO public."schema_migrations" (version) VALUES (20241029080754); INSERT INTO public."schema_migrations" (version) VALUES (20241029082533); INSERT INTO public."schema_migrations" (version) VALUES (20241029151959); -INSERT INTO public."schema_migrations" (version) VALUES (20241101123601); +INSERT INTO public."schema_migrations" (version) VALUES (20241104115340);