Packages
ash
3.24.3
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/notifier/notifier.ex
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Notifier do
@moduledoc """
A notifier is an extension that receives various events
"""
@callback notify(Ash.Notifier.Notification.t()) :: any()
@callback requires_original_data?(Ash.Resource.t(), Ash.Resource.Actions.action()) :: boolean
@doc """
A load statement to be applied before this notifier receives the notification.
The loaded fields are merged onto `notification.data` before `notify/1` is called.
If multiple notifiers request the same fields with the same arguments, the
calculation dependency resolver ensures they are only loaded once.
The return value can be anything accepted by `Ash.Query.load/2`, including
`%Ash.Query.Calculation{}` structs for multi-level dependency resolution
(e.g. a PubSub notifier building one inner load per publication).
"""
@callback load(Ash.Resource.t(), Ash.Resource.Actions.action()) ::
atom | [atom] | Keyword.t()
@optional_callbacks load: 2
require Ash.Tracer
require Logger
@doc false
@spec notify(module(), Ash.Notifier.Notification.t()) :: any()
def notify(notifier_module, notification) do
apply(notifier_module, :notify, [notification])
end
@doc false
@spec requires_original_data?(module(), Ash.Resource.t(), Ash.Resource.Actions.action()) ::
boolean
def requires_original_data?(notifier_module, resource, action) do
Ash.BehaviourHelpers.call_and_validate_return(
notifier_module,
:requires_original_data?,
[resource, action],
[true, false],
behaviour: __MODULE__,
callback_name: "requires_original_data?/2"
)
end
@doc false
@spec load(module(), Ash.Resource.t(), Ash.Resource.Actions.action()) ::
atom() | [atom()] | Keyword.t()
def load(notifier_module, resource, action) do
result = apply(notifier_module, :load, [resource, action])
# Accept atom or list (load can return list of atoms, keyword list, or other load shapes)
if is_atom(result) or is_list(result) do
result
else
raise Ash.Error.Framework.InvalidReturnType,
message: """
Invalid value returned from #{inspect(notifier_module)}.load/2.
The callback #{inspect(__MODULE__)}.load/2 expects an atom, list of atoms, or Keyword.t().
"""
end
end
defmacro __using__(_opts) do
quote do
@behaviour Ash.Notifier
def requires_original_data?(_, _), do: false
def load(_, _), do: []
defoverridable requires_original_data?: 2, load: 2
end
end
@doc """
Builds a query with `NotifierDependencies` calculations for all notifiers
that implement `load/2` with non-empty load statements.
This is meant to be merged into the load that already happens in the action
pipeline, so that notifier dependencies are loaded in bulk (once per batch)
rather than per-notification at dispatch time.
"""
@spec notifier_calculation_query(Ash.Resource.t(), Ash.Resource.Actions.action(), map()) ::
Ash.Query.t() | nil
def notifier_calculation_query(resource, action, source_context \\ %{}) do
notifiers = Ash.Resource.Info.notifiers(resource) ++ List.wrap(Map.get(action, :notifiers))
notifier_statements =
Enum.reduce(notifiers, %{}, fn notifier, acc ->
statement =
if function_exported?(notifier, :load, 2) do
load(notifier, resource, action)
else
[]
end
case List.wrap(statement) do
[] -> acc
statement -> Map.put(acc, notifier, statement)
end
end)
if map_size(notifier_statements) == 0 do
nil
else
Enum.reduce(
notifier_statements,
Ash.Query.new(resource),
fn {notifier, statement}, query ->
Ash.Query.calculate(
query,
{Ash.Notifier.NotifierDependencies, notifier: notifier},
Ash.Type.Map,
{Ash.Notifier.NotifierDependencies, [statement: statement, notifier: notifier]},
%{},
[],
%{},
source_context: source_context
)
end
)
end
end
@doc """
Extracts pre-loaded notifier dependency data from a record's calculations
and builds the `notifier_data` map used by `enrich_notification/3`.
Returns `{notifier_statements, notifier_data}` where `notifier_data` maps
each notifier to `{statement, extra}`.
"""
@spec extract_notifier_data(
Ash.Resource.record(),
[module()],
Ash.Resource.t(),
Ash.Resource.Actions.action()
) ::
{map(), map()}
def extract_notifier_data(record, notifiers, resource, action) do
notifier_statements =
Enum.reduce(notifiers, %{}, fn notifier, acc ->
statement =
if function_exported?(notifier, :load, 2) do
load(notifier, resource, action)
else
[]
end
case List.wrap(statement) do
[] -> acc
statement -> Map.put(acc, notifier, statement)
end
end)
calculations = Map.get(record, :calculations) || %{}
notifier_data =
Map.new(notifier_statements, fn {notifier, statement} ->
extra =
Map.get(
calculations,
{Ash.Notifier.NotifierDependencies, notifier: notifier},
%{}
)
{notifier, {statement, extra}}
end)
{notifier_statements, notifier_data}
end
@doc """
Sends any notifications that can be sent, and returns the rest.
A notification can only be sent if you are not currently in a transaction
for the resource in question.
"""
@spec notify(list(Ash.Notifier.Notification.t()) | Ash.Notifier.Notification.t()) ::
list(Ash.Notifier.Notification.t())
def notify([]), do: []
def notify(resource_notifications) do
{unsent, to_send} =
resource_notifications
|> List.wrap()
|> Enum.group_by(& &1.resource)
|> Enum.split_with(fn {resource, _} ->
resource && Ash.DataLayer.in_transaction?(resource)
end)
for {resource, notifications} <- to_send, notification <- notifications do
notifiers =
case notification.for do
nil -> Ash.Resource.Info.notifiers(resource)
allowed_notifiers -> Enum.uniq(List.wrap(allowed_notifiers))
end
{notification, notifier_data} =
if has_preloaded_notifier_data?(notification.data) do
# Data was pre-loaded in the action pipeline
{_statements, notifier_data} =
extract_notifier_data(notification.data, notifiers, resource, notification.action)
{notification, notifier_data}
else
# Fallback for manually-created notifications or paths that skip the pipeline
load_notification_data(notification, notifiers)
end
for notifier <- notifiers do
enriched = enrich_notification(notification, notifier, notifier_data)
do_notify(notifier, %{enriched | from: self()})
end
end
unsent
|> Enum.map(&elem(&1, 1))
|> List.flatten()
end
# Checks if the record already has NotifierDependencies calculations loaded
# (meaning the action pipeline pre-loaded them).
defp has_preloaded_notifier_data?(data) when is_struct(data) do
case Map.get(data, :calculations) do
calcs when is_map(calcs) and map_size(calcs) > 0 ->
Enum.any?(calcs, fn
{{Ash.Notifier.NotifierDependencies, _}, _} -> true
_ -> false
end)
_ ->
false
end
end
defp has_preloaded_notifier_data?(_), do: false
# Builds one NotifierDependencies calculation per notifier that has a
# non-empty load statement, then does a single Ash.load so the calculation
# dependency resolver can deduplicate identical underlying loads.
# This is the fallback path for notifications that weren't pre-loaded
# in the action pipeline.
defp load_notification_data(notification, notifiers) do
notifier_statements =
Enum.reduce(notifiers, %{}, fn notifier, acc ->
statement =
if function_exported?(notifier, :load, 2) do
load(notifier, notification.resource, notification.action)
else
[]
end
case List.wrap(statement) do
[] -> acc
statement -> Map.put(acc, notifier, statement)
end
end)
if map_size(notifier_statements) == 0 do
{notification, %{}}
else
domain =
notification.domain ||
Ash.Resource.Info.domain(notification.resource)
source_context =
(notification.changeset && notification.changeset.context) || %{}
query =
Enum.reduce(
notifier_statements,
Ash.Query.new(notification.resource),
fn {notifier, statement}, query ->
Ash.Query.calculate(
query,
{Ash.Notifier.NotifierDependencies, notifier: notifier},
Ash.Type.Map,
{Ash.Notifier.NotifierDependencies, [statement: statement, notifier: notifier]},
%{},
[],
%{},
source_context: source_context
)
end
)
tenant =
get_in(notification, [Access.key(:changeset), Access.key(:tenant)]) ||
get_in(notification, [Access.key(:data), Access.key(:__metadata__), :tenant])
case Ash.load(
notification.data,
query,
domain: domain,
tenant: tenant,
authorize?: false,
context: %{private: %{internal?: true}}
) do
{:ok, loaded_data} ->
notifier_data =
Map.new(notifier_statements, fn {notifier, statement} ->
extra =
Map.get(
loaded_data.calculations || %{},
{Ash.Notifier.NotifierDependencies, notifier: notifier},
%{}
)
{notifier, {statement, extra}}
end)
{%{notification | data: loaded_data}, notifier_data}
{:error, error} ->
Logger.warning("""
Failed to load notification data for #{inspect(notification.resource)}.#{notification.action.name}: \
#{inspect(error)}
""")
{notification, %{}}
end
end
end
# Merges the notifier's dependency map onto notification.data.
# Placement is determined by the original load statement intent:
# attributes, relationships, and aggregates go directly on the struct;
# calculations without a `load` field go into .calculations.
defp enrich_notification(notification, notifier, notifier_data) do
case Map.get(notifier_data, notifier) do
nil ->
notification
{_statement, extra} when map_size(extra) == 0 ->
notification
{statement, extra} ->
resource = notification.resource
enriched_data =
Enum.reduce(extra, notification.data, fn {key, value}, data ->
case placement_for_key(key, statement, resource) do
:struct -> Map.put(data, key, value)
:calculations -> Map.update!(data, :calculations, &Map.put(&1 || %{}, key, value))
end
end)
%{notification | data: enriched_data}
end
end
# Determines whether a key from the notifier's extra map belongs on the
# resource struct directly or in record.calculations, based on resource schema
# and any %Ash.Query.Calculation{} structs in the original statement.
@doc false
def placement_for_key(key, statement, resource) do
calc_struct =
Enum.find(List.wrap(statement), fn
%Ash.Query.Calculation{name: ^key} -> true
_ -> false
end)
case calc_struct do
%Ash.Query.Calculation{load: load} when not is_nil(load) ->
:struct
%Ash.Query.Calculation{} ->
:calculations
nil ->
cond do
Ash.Resource.Info.attribute(resource, key) ->
:struct
Ash.Resource.Info.relationship(resource, key) ->
:struct
Ash.Resource.Info.aggregate(resource, key) ->
:struct
Ash.Resource.Info.calculation(resource, key) ->
:calculations
true ->
:calculations
end
end
end
defp do_notify(notifier, notification) do
tracer = notification.changeset && notification.changeset.context[:private][:tracer]
domain = notification.domain || Ash.Resource.Info.domain(notification.resource)
Ash.Tracer.span :notification,
fn -> Ash.Domain.Info.span_name(domain, notification.resource, :notifier) end,
tracer do
metadata = fn ->
%{
domain: domain,
notifier: notifier,
resource: notification.resource,
resource_short_name: Ash.Resource.Info.short_name(notification.resource),
actor: notification.changeset && notification.changeset.context[:private][:actor],
tenant: notification.changeset && notification.changeset.context[:private][:tenant],
action: notification.action.name,
authorize?:
notification.changeset && notification.changeset.context[:private][:authorize?]
}
end
Ash.Tracer.set_metadata(tracer, :action, metadata)
Ash.Tracer.telemetry_span [:ash, :notifier], metadata do
notify(notifier, notification)
end
end
end
end