defmodule Runbox.Scenario.OutputAction do
@moduledoc group: :output_actions
@moduledoc """
Output action represents a side effect produced by a run.
## Creating output actions
Creating output action typically happens from scenario. It is done by
creating one of the appropriate struct:
* `t:Runbox.Scenario.OutputAction.UpsertAssetAttributes.t/0`
* `t:Runbox.Scenario.OutputAction.DeleteAssetAttributes.t/0`
* `t:Runbox.Scenario.OutputAction.DeleteAllAssetAttributes.t/0`
* `t:Runbox.Scenario.OutputAction.UpsertEdge.t/0`
* `t:Runbox.Scenario.OutputAction.DeleteEdge.t/0`
* `t:Runbox.Scenario.OutputAction.Notification.t/0`
* `t:Runbox.Scenario.OutputAction.Event.t/0`
* `t:Runbox.Scenario.OutputAction.ExecuteSQL.t/0`
* `t:Runbox.Scenario.OutputAction.Incident.t/0`
* `t:Runbox.Scenario.OutputAction.IncidentPatch.t/0`
For example:
iex> %OutputAction.UpsertAssetAttributes{
...> type: "/asset/camera",
...> id: "one",
...> attributes: %{"foo" => "bar"}
...> }
## Asset and edge output actions
Changes done by output actions to assets and edges are scoped for each
scenario run. The global result is computed from the changes done by
individual runs.
A single run is forbidden to do multiple changes to the same object (asset
attribute or edge) at the same time. However distinct runs can perform such
changes, because they are scoped to each run.
The rules to compute the global state apply in the following order:
1. Change with the higher timestamp has precedence.
1. Updating an asset attribute or an edge has precedence to deleting it.
1. Change with the higher value has precedence (applies only for asset attributes).
"""
alias __MODULE__, as: OA
@typedoc """
Asset type.
Assets are grouped by their asset types. Assets of the same type should be of similar nature
(e.g. servers or buildings) and should share the same set of attributes.
Asset types must start with a slash and must not end with a slash. Slashes can be used
within the asset type to denote structure. For example, these are valid asset types:
`/assets/servers`, `/servers`, and `/assets/network/routers`.
Although asset types like `/servers` are permitted, it is recommended to prefix asset types
with the `/assets` prefix to enable consistent configuration of common attributes (e.g., `name`)
via the root `/assets` asset type in the UI.
"""
@type asset_type :: String.t()
@typedoc """
Asset ID.
Asset ID together with an asset type uniquely identifies an asset.
Asset ID must not contain any slashes. They can contain any other character though.
For example, these are valid asset IDs: `fire_alarm`, `192.168.0.142`, `Meeting room`, and
`Příliš žluťoučký kůň`.
"""
@type asset_id :: String.t()
@type edge_type :: String.t()
@type access_tag :: String.t()
@typedoc """
Attribute - access tag mapping.
Access tags are defined as map where keys are attributes and values
are tags of these attributes.
Attributes (keys) can be defined as:
* `:all` - all attributes in this upsert share the same access tags
* `String.t()` - single attribute access tags definition
* `[String.t()]` - list of attributes that share the same access tags
Access tags (values) can be defined by single access tag or list of access tags.
Using this map structure we can define shared access tag for multiple attributes
as well as multiple access tag for single attribute (or combination of both).
Attributes can be defined with `dot` syntax (e.g. `user.contact.address.street`)
where nested attributes are separated by `.`. We can use this syntax to define
access tags for group of nested attributes - access tags defined for
`user.contact.address` will be used for all nested attributes such as
`user.contact.address.city` and `user.contact.address.street`.
For delete operations: `DeleteAllAssetAttributes`,`DeleteAssetAttributes` and `DeleteEdge`
it is recommended to use the same access tags as those used for the corresponding upsert
operations within the same run.
Overlapping access tags definitions are concatenated. For example access tags
defined as:
```
%UpsertAssetAttributes{
...
access_tags: %{
all: ["tag1"],
"user.contact" => ["tag2"],
["user.contact.address", "user.contact.phone"] => ["tag3"],
"user.contact.address.street" => "tag4"
}
}
```
would mean that:
* `"user.contact"` has these tags defined `["tag1", "tag2"]`
* `"user.contact.address.phone"` has these tags defined `["tag1", "tag2", "tag3"]`
* `"user.contact.address.street"` has these tags defined `["tag1", "tag2", "tag3", "tag4"]`
"""
@type access_tags :: %{
(attribute :: :all | String.t() | [String.t()]) => access_tag() | [access_tag()]
}
defmodule UpsertAssetAttributes do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Upsert Asset Attributes.
The resulting output action will create or update attributes of the given
asset.
"""
@enforce_keys [:type, :id, :attributes]
defstruct [:type, :id, :attributes, access_tags: %{}]
@typedoc """
Upsert Asset Attributes
* `:type` - asset type (for more information, see `t:asset_type/0`)
* `:id` - asset ID (for more information, see `t:asset_id/0`)
* `:attributes` - a map of attributes in format `%{"name" => value}` to be
inserted or updated
* `:access_tags` - a map of attributes mapped to its access tags (for more information,
see `t:Runbox.Scenario.OutputAction.access_tags/0`)
"""
@type t :: %__MODULE__{
type: OA.asset_type(),
id: OA.asset_id(),
attributes: map(),
access_tags: OA.access_tags()
}
end
defmodule DeleteAssetAttributes do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Delete Asset Attributes.
The resulting output action will delete attributes of the given asset. This
action succeeds even if the attributes do not exist.
"""
@enforce_keys [:type, :id, :attributes]
defstruct [:type, :id, :attributes, access_tags: %{}]
@typedoc """
Delete Asset Attributes
* `:type` - asset type
* `:id` - asset ID
* `:attributes` - a map of attributes to be deleted
* `:access_tags` - a map of attributes mapped to its access tags (for more information,
see `t:Runbox.Scenario.OutputAction.access_tags/0`)
The leaf attributes of the `:attributes` map are deleted.
E.g., if asset has the following attributes
```elixir
%{
"a": 1,
"b": %{"c" => 2}
}
```
and the following `:attributes` are provided in the output action
```elixir
%{
"b": %{"c" => true}
}
```
then `b.c` attribute will be deleted, resulting in
```elixir
%{
"a": 1
"b": %{}
}
```
"""
@type t :: %__MODULE__{
type: OA.asset_type(),
id: OA.asset_id(),
attributes: map(),
access_tags: OA.access_tags()
}
end
defmodule DeleteAllAssetAttributes do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Delete All Asset Attributes.
The resulting output action will delete all existing attributes of the
given asset. This is scoped to the attributes created by the same run.
"""
@enforce_keys [:type, :id]
defstruct [:type, :id, access_tags: %{}]
@typedoc """
Delete All Asset Attributes
* `:type` - asset type
* `:id` - asset ID
* `:access_tags` - a map of attributes mapped to its access tags (for more information,
see `t:Runbox.Scenario.OutputAction.access_tags/0`)
"""
@type t :: %__MODULE__{
type: OA.asset_type(),
id: OA.asset_id(),
access_tags: OA.access_tags()
}
end
defmodule UpsertEdge do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Upsert Edge.
The resulting output action will create an edge between two assets. The
assets do not have to exist at the time of the edge creation.
"""
@enforce_keys [:from_type, :from_id, :to_type, :to_id, :type]
defstruct [:from_type, :from_id, :to_type, :to_id, :type, access_tags: []]
@typedoc """
Upsert Edge
* `:from_type` - type of the asset which this edge goes out from
* `:from_id` - id of the asset which this edge goes out from
* `:to_type` - type of the asset which this edge goes to
* `:to_id` - id of the asset which this edge goes to
* `:type` - type of the edge
* `:access_tags` - list of access tags for this edge (for more information,
see `t:Runbox.Scenario.OutputAction.access_tags/0`)
"""
@type t :: %__MODULE__{
from_type: OA.asset_type(),
from_id: OA.asset_id(),
to_type: OA.asset_type(),
to_id: OA.asset_id(),
type: OA.edge_type(),
access_tags: [OA.access_tag()]
}
end
defmodule DeleteEdge do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Delete Edge.
The resulting output action will delete an edge between two assets. This
action succeeds even if the edge does not exist.
"""
@enforce_keys [:from_type, :from_id, :to_type, :to_id, :type]
defstruct [:from_type, :from_id, :to_type, :to_id, :type, access_tags: []]
@typedoc """
Delete Edge
* `:from_type` - type of the asset which this edge goes out from
* `:from_id` - id of the asset which this edge goes out from
* `:to_type` - type of the asset which this edge goes to
* `:to_id` - id of the asset which this edge goes to
* `:type` - type of the edge
* `:access_tags` - list of access tags for this edge (for more information,
see `t:Runbox.Scenario.OutputAction.access_tags/0`)
"""
@type t :: %__MODULE__{
from_type: OA.asset_type(),
from_id: OA.asset_id(),
to_type: OA.asset_type(),
to_id: OA.asset_id(),
type: OA.edge_type(),
access_tags: [OA.access_tag()]
}
end
defmodule Notification do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Notification.
The resulting output action sends a notification.
"""
defmodule AssetActor do
@moduledoc group: :output_actions
@moduledoc """
Struct for referencing assets in Notifications.
"""
@enforce_keys [:type, :id]
defstruct [:type, :id]
@type t :: %__MODULE__{type: OA.asset_type(), id: OA.asset_id()}
end
defmodule IncidentActor do
@moduledoc group: :output_actions
@moduledoc """
Struct for referencing incidents in Notifications.
"""
@enforce_keys [:type, :id]
defstruct [:type, :id]
@type t :: %__MODULE__{type: String.t(), id: String.t()}
end
@enforce_keys [:type, :data]
defstruct type: nil,
data: nil,
primary_actor: nil,
actors: [],
priority: :medium,
metadata: %{},
direct_subscriptions: nil,
attachments: nil,
email_reply_to: nil,
aqls: %{}
@typedoc """
Notification
Structure Notification defines the possible attributes of notifications.
Different types of notifications are distinguished by the use of different
attributes and their specific values. The specific use of the Notification
structure in the source code defines the pattern according to which
the final notifications of a given type will be generated.
Final notifications are sent to recipients according to the rules defined
in Notification Groups or in `direct_subsctriptions` attribute of the Notification
structure desribed below.
## Example
%Notification{
type: "technician_entry",
data: %{
"room_id" => %{type: "/assets", id: "server_room"},
"technician_id" => %{type: "/assets/cardholder", id: "j_adams"}
},
priority: :medium
direct_subscriptions: [
%{
"channels" => ["smtp"],
"templates" => ["operator"],
"user" => %{"email" => "operator@acme.com"}
}
],
email_reply_to: %{
"Technical support" => "technician@acme.com"
},
aqls: %{
"asset" => %{
"aql" => "FROM '/assets/server_room' SELECT [\"name\"]",
"primary" => true
}
},
}
Notification Attributes
* `:type` - string.
Type of the notification.
The scenario manifest defines notification types and some additional metadata about each type.
See details in `t:Runbox.Scenario.Manifest.t/0` attribute `notifications`.
The `type` identifier is used, along with other criteria, to find the template through which the notification
will be transformed into the final notification intended for sending to a specific communication channel.
* `:data` - map.
A map of data that can be used within the notification template.
Keys of the map are used to reference the data.
## Example
# Definition of the data attribute
%{
"room_id" => %{type: "/assets", id: "server_room"},
"technician_id" => %{type: "/assets/cardholder", id: "j_adams}"
},
# Usage of the data attribute in the template
<%= inspect(data["technician_id"]) %>
* `:primary_actor` - the primary actor of this notification, optional.
The reference to the asset or incident to which the notification relates.
If a primary asset is included in the notification then the notification is not sent to
recipients who do not have permission to see the primary asset. Use
`Runbox.Scenario.OutputAction.Notification.AssetActor` and
`Runbox.Scenario.OutputAction.Notification.IncidentActor` to reference the entities.
* `:actors` - list of related entities/actors, optional.
The notification will be sent only to those recipients who have the right to see
the specified entities. These can be Assets
(`Runbox.Scenario.OutputAction.Notification.AssetActor`) or Incidents
(`Runbox.Scenario.OutputAction.Notification.IncidentActor`).
* `priority` - optional, atom.
Possible value are `:low`, `:medium`, `:high`.
Defaults to `:medium`.
Only applicable to some channels.
Attribute is not used for prioritization of email notifications.
* `:metadata` - map, optional.
Additional metadata which can be used to filter recipients.
Metadata filter can be set in the User UI
(menu `NOTIFICATIONS / Notification groups / Metadata Filter`).
Metadata should adhere to the format specified in `t:Runbox.Scenario.Manifest.t/0`
attribute `notifications`.
## Example
# metadata attribute
%{"report_for" => "C-Level"}
* `:direct_subscriptions` - list of maps, optional.
Direct subscriptions provide a way to bypass routing system defined
through notification groups and send notifications to the specific
recipients directly.
It is even possible to send notifications to recipients who do not have
an Altworx account.
Each member of the list is a map with the following structure:
* `"user"` - is a map that with keys:
* `"email"` - the value is a string containing email address.
* `"notification"` - optional. The value is a map with key:
* `"language"` - optional. String containing language of the notification template.
Type of the channel determines which keys are used.
* `"templates"` - list of template types that will be used to generate the final notification.
Templates itselves are located either in the `priv/notifications/[scenario_id]/templates/[notification_type]`
directory or in `deployment/scenario_notification_overrides/[scenario_id]/templates/[notification_type]`
directory.
* `"channels"` - list of channel ids through which notifications will be sent.
Channels are defined in the Altworx Admin UI (menu `NOTIFICATIONS / CHANNELS`).
## Example
# Direct_subscriptions attribute
[
%{
"channels" => ["smtp"],
"templates" => ["operator"],
"user" => %{
"email" => "operator@acme.com",
"notification" => %{"language" => "en"}
}
}
]
* `:attachments` - list of maps, optional.
Attachments that will be attached to the notification.
Each map has the following keys:
* `:data` - binary. Content of the attachment.
* `:filename` - string. Name of the file.
* `:content_type` - string, optional. MIME type of the data.
* `:type` - whether the attachment is to be inlined in the template (`:inline`)
or not (`:attachment`, this is the default).
## Example
# Definition of the attachments attribute
%{
data: "greeting",
content_type: "text/plain",
filename: "greeting.txt",
type: :attachment
}
* `:email_reply_to` - map, optional.
If the `email_reply_to` attribute is defined, the appropriate `Reply-To` headers
will be inserted into the email protocol.
The recipient of the notification will see the information as the email addresses
to which his possible reply will be sent.
If the attribute `email_reply_to` is not specified the value defined
in the `reply_to` parameter of the `config.ini` configuration file
stored in the deployment directory is used.
Keys are the names of the recipients of the reply.
Values are their email addreses.
## Example
# Definition of the email_reply_to attribute
%{
"Call Centrum" => "info@acme.com"
}
* `:aqls` - map of AQL defintions, optional.
`AQL` enables to add more context to the final notification.
`AQL` also allows to simplify the scenario code. For example, if you need to insert the name
of an asset that the notification references into the text of the notification,
you can either manage that name yourself in scenario state or you can fetch the name using AQL.
Without `AQL` the scenario has to maintain the asset name in the scenario state,
handle messages that change the asset name coming from the input topics and pass the name
to the template via the `data` attribute. All of this is usually unrelated to the scenario
logic and only serves to create a better text of the notification.
Using `AQL`, developer passes an `AQL` query to the notification. This query uses an asset ID
to retrieve the current asset name from the reality network at the time the final notification
is created. The scenario usually needs the asset ID for its own purposes, so it is probably
stored in the scenario state anyway.
The keys of the `aqls` are the string identifiers under which the `AQL` query results
are available inside the notification template.
The values are maps providing more information about the query. Each map has the following keys:
* `aql` - string. Contains the `AQL` query.
* `primary` - boolean atom:
* `true` - notifications will be sent only to those recipients who have read permission
to all the assets returned by the query.
* `false` - permissions are not evaluated.
## Example
# Definition of the `aqls` attribute
%{
"asset" => %{
"aql" => "FROM '/assets/server_room/*' SELECT [\"name\"]",
"primary" => false
}
}
# Value passed to the notification template after above AQL queries are executed:
%{
"asset" => [
%AssetMap.Ecto.Model.Asset{
type: "/assets/server_room",
id: "Z10",
attributes: %{"name" => "022-000033/AGPP"}
}
]
}
# The name of the technician can be obtained inside the notification template
# with the following command
<%= List.first(aqls["asset"]).attributes["name"] %>
"""
@type t :: %__MODULE__{
type: String.t() | atom(),
data: map(),
primary_actor: AssetActor.t() | IncidentActor.t() | nil,
actors: [AssetActor.t() | IncidentActor.t()],
aqls: map(),
priority: :low | :medium | :high | String.t(),
metadata: map(),
direct_subscriptions: [map()] | nil,
attachments: [map()] | nil,
email_reply_to: map() | nil
}
end
defmodule Event do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Event.
The resulting output action creates an event.
See `t:t/0` for more information.
> #### Note {: .warning}
>
> Beware that creating incident history manually via event's `incident_actors` property, instead
> of relying on Incident output actions, is low-level and prone to errors. You must always ensure
> that you create the event **and** you accordingly update the incident itself. Failing to do
> either will result in inconsistent state.
"""
@enforce_keys [:type, :template, :actors]
defstruct type: nil,
template: nil,
actors: nil,
incident_actors: nil,
params: %{},
origin_messages: []
@typedoc """
Event
* `:type` - type of the event. It's declared in Scenario's Manifest.
* `:template` - event's template that allows to interpolate actors and parameters
(see the *Interpolation* section below).
* `:actors` - map of asset actors that are to be linked with the event. They may also be
interpolated in the template.
* `:incident_actors` - map of incident actors that are to be linked with the event. This means
the event is either part of the incident's history, or is just related. They may also be
interpolated in the template. See `t:incident_actor/0` for more information.
* `:params` - map of template parameters that are interpolated in the template
(optional, defaults to `%{}`).
* `:origin_messages` - list of references to raw messages linked with the event itself
(optional, defaults to `[]`).
## Interpolation
The template may reference actors using placeholders like `${actors.actor_key}`, where
`actor_key` corresponds to a key in the `actors` map within this struct. In the UI,
these placeholders are transformed into links. Each link points to the corresponding
asset and displays the asset's name as the link text.
Incident actors can be interpolated in a very similar manner using `incidents` keyword -
`${incidents.fire}`.
The template may also contain placeholders like `${params.param_key}`, where `param_key`
is a key under the `params` map. These placeholders are simply replaced with the
corresponding values from the `params` map. These parameters are useful for dynamic
content other than actors.
## Example
%Event{
type: "server_login",
template: "${actors.person} logged into ${actors.server} using OpenSSH ${params.openssh_version}",
actors: %{
"person" => %{
asset_type: "/assets/person",
asset_id: "joe"
},
"server" => %{
asset_type: "/assets/server",
asset_id: "192.168.142.18"
}
},
params: %{"openssh_version" => "9.8"},
origin_messages: [normalized_message.origin]
}
"""
@type t :: %__MODULE__{
type: String.t() | atom(),
template: String.t(),
actors: actors(),
incident_actors: incident_actors(),
params: %{String.t() => String.t()},
origin_messages: [Runbox.Message.origin()]
}
@type actors :: %{(actor_key :: String.t()) => actor()}
@type actor :: %{
asset_type: String.t(),
asset_id: String.t()
}
@type incident_actors :: %{(actor_key :: String.t()) => incident_actor()}
@typedoc """
Incident actor of the event.
Can mean two things depending on if `status` and `severity` is set. If either is empty, then the
event is considered related to the incident, similar to `actors`. If both are set, then the
event is considered a part of the incident's history. `status` and `severity` then represent the
change of these incident properties at the time of the event. `attributes` is fully optional
and contains further context about the change of the incident.
"""
@type incident_actor :: %{
:type => String.t(),
:id => String.t(),
optional(:status) => String.t() | nil,
optional(:severity) => 1 | 2 | 3 | 4 | nil,
optional(:attributes) => map() | nil
}
end
defmodule ExecuteSQL do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Execute SQL.
The resulting output action executes an SQL statement.
"""
@enforce_keys [:db_connection, :sql_query, :data, :type]
defstruct db_connection: nil,
sql_query: nil,
data: nil,
type: nil
@typedoc """
Execute SQL
* `:db_connection` - map with database connection info (`hostname`, `username`, `password`, `database`)
* `:sql_query` - SQL query.
* `:data` - list of parameterized data.
* `:type` - database type, right now only PostgreSQL is supported. It's checked in `OutputProcessor` and
the value must be `:postgresql`.
If `:run_id` is present in `data`, it's replaced with actual run_id in `AssetMap.Api.SqlExecutor`.
"""
@type t :: %__MODULE__{
db_connection: map(),
sql_query: String.t(),
data: list(),
type: :postgresql | String.t()
}
end
@typedoc """
String with interpolation support.
Supports interpolating asset references to a format which is useful for
displaying assets in the Altworx UI. The string can contain any number of
placeholders in format
${assets.["/assets/camera", "one"]}
Where `"/assets/camera"` is the asset type and `"one"` is the asset ID. The resulting interpolation is
[Camera One[/assets/camera/one]]
Where `Camera One` is the asset's `name` attribute and `/assets/camera/one`
is the asset's full ID. When the asset `name` is not present, the asset's ID
is used instead
[one[/assets/camera/one]]
When the asset cannot be found, the resulting interpolation is
unknown
The interpolation is done upon "read" operations, therefore the result can
vary based on the asset visibility for the identity of the user performing
the operation.
"""
@type interpolable :: String.t()
defmodule IncidentFuture do
@moduledoc group: :output_actions
@moduledoc """
Incident Future.
The struct represents a future possible change of an Incident. It carries information about how
the Incident might change.
The change can be expected to happen at a specified time (then `timestamp` is a positive integer
- epoch time in ms) or it is unknown when the change might occur (then `timestamp` is `nil`).
"""
@enforce_keys [
:status,
:severity,
:timestamp,
:description
]
defstruct [
:status,
:severity,
:timestamp,
:description
]
@type t :: %IncidentFuture{
status: String.t(),
severity: OA.Incident.severity(),
timestamp: integer() | nil,
description: OA.interpolable()
}
end
defmodule IncidentHistory do
@moduledoc group: :output_actions
@moduledoc """
Incident history.
The struct represents a change of an Incident in time. It carries information about how the
Incident changed, all describe how the Incident looks after the change. The struct contains all
necessary parameters to create an Incident history record.
Since Incident history is now synonymous to Events, `event_*` fields of this struct can be used
to control how the final Event looks like.
Available fields
- `:status` - status of the Incident
- `:severity` - severity of the Incident
- `:timestamp` - time of the change
- `:description` - description of the change - what has happened, will be used as Event template
- `:attributes` - (optional) significant additional attributes that changed with the Incident
(usually metrics, like temperature, server load, distance). Can be empty.
- `:event_type` - (optional) type of the underlying Event, if empty a default type
`incident_updated` will be used
- `:event_actors` - (optional) additional actors of the underlying Event. If empty no actors are
defined.
- `:event_params` - (optional) additional parameters of the Event. If empty no params are
defined.
- `:event_origin_messages` - (optional) list of references to raw messages linked with the
underlying Event. If empty no raw messages are linked with the Event.
"""
@enforce_keys [
:status,
:severity,
:timestamp,
:description
]
defstruct [
:status,
:severity,
:timestamp,
:description,
:attributes,
:event_type,
:event_actors,
:event_params,
:event_origin_messages
]
@typedoc """
Incident history.
Note Incident history is synonymous to Events and thus carries similar parameters.
- `:status` - status of the Incident
- `:severity` - severity of the Incident
- `:timestamp` - time of the change
- `:description` - description of the change - what has happened, will be used as Event template
- `:attributes` - (optional) significant additional attributes that changed with the Incident
(usually metrics, like temperature, server load, distance). Can be empty.
- `:event_type` - (optional) type of the underlying Event, if empty a default type
`incident_updated` will be used
- `:event_actors` - (optional) additional actors of the underlying Event. If empty no actors are
defined.
- `:event_params` - (optional) additional parameters of the Event. If empty no params are
defined.
- `:event_origin_messages` - (optional) list of references to raw messages linked with the
underlying Event. If empty no raw messages are linked with the Event.
"""
@type t :: %IncidentHistory{
status: String.t(),
severity: OA.Incident.severity(),
timestamp: integer(),
description: OA.interpolable(),
attributes: map() | nil,
event_type: String.t() | nil,
event_actors: Event.actors() | nil,
event_params: %{String.t() => String.t()} | nil,
event_origin_messages: [Runbox.Message.origin()] | nil
}
end
defmodule IncidentActor do
@moduledoc group: :output_actions
@moduledoc "Incident Actor"
@enforce_keys [
:type,
:id
]
defstruct [
:type,
:id
]
@type t :: %IncidentActor{
type: String.t(),
id: String.t()
}
end
defmodule Incident do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Incident.
The resulting output action creates an incident.
"""
@enforce_keys [
:type,
:id,
:subject,
:status,
:resolved,
:severity,
:future,
:history
]
defstruct [
:type,
:id,
:subject,
:status,
:resolved,
:severity,
:future,
:history,
:actors,
:user_actions,
:additional_attributes
]
@type severity :: 1..4
@typedoc """
User actions that can be fired on the incident.
The value of a user action is a JWT obtained by calling
`Runbox.Scenario.UserAction.pack/3`.
Each user action has a key by which it can later be referenced in
`Runbox.Scenario.OutputAction.IncidentPatch` and either modified or deleted.
## Example
{:ok, action} = UserAction.pack("cam_actions", "close_incident", %{...})
%{"close" => action}
"""
@type user_actions :: %{optional(key :: String) => Joken.bearer_token()}
@typedoc """
Incident
"""
@type t :: %Incident{
type: String.t(),
id: String.t(),
subject: OA.interpolable(),
status: String.t(),
resolved: boolean(),
severity: severity(),
future: [IncidentFuture.t()],
history: [IncidentHistory.t()],
actors: [IncidentActor.t()] | nil,
user_actions: user_actions() | nil,
additional_attributes: map() | nil
}
end
defmodule IncidentPatch do
@moduledoc group: :output_actions
@moduledoc """
Parameters for output action Incident Patch.
The resulting output action updates an incident.
"""
@enforce_keys [
:type,
:id
]
defstruct [
:type,
:id,
:subject,
:status,
:resolved,
:severity,
:future,
:history,
:actors,
:user_actions,
:additional_attributes
]
@typedoc """
Incident Patch
"""
@type t :: %IncidentPatch{
type: String.t(),
id: String.t(),
subject: OA.interpolable() | nil,
status: String.t() | nil,
resolved: boolean() | nil,
severity: OA.Incident.severity() | nil,
future: [IncidentFuture.t()] | nil,
history: [IncidentHistory.t()] | nil,
actors:
%{
optional(:add) => [IncidentActor.t()],
optional(:remove) => [IncidentActor.t()]
}
| nil,
user_actions:
%{
optional(:upsert) => Incident.user_actions(),
optional(:remove) => [key :: String.t()]
}
| nil,
additional_attributes:
%{
optional(:upsert) => map(),
optional(:remove) => list()
}
| nil
}
end
defmodule BadOutputAction do
@moduledoc group: :output_actions
@moduledoc """
Body of invalid output action.
"""
@enforce_keys [:params]
defstruct [:params]
@typedoc """
Bad Output Action
* `:params` - holds the original parameters
"""
@type t :: %__MODULE__{
params: any()
}
end
@type oa_params ::
UpsertAssetAttributes.t()
| DeleteAssetAttributes.t()
| DeleteAllAssetAttributes.t()
| UpsertEdge.t()
| DeleteEdge.t()
| Notification.t()
| Event.t()
| ExecuteSQL.t()
| Incident.t()
| IncidentPatch.t()
defstruct [:body, :timestamp, :scenario_id, :run_id]
@typedoc """
Output Action
This struct is produced by `Runbox.Runtime.Stage.Sandbox.execute_run/3`.
"""
@type t :: %__MODULE__{
body: oa_params() | BadOutputAction.t(),
timestamp: integer()
}
end