Skip to content

Commit 4e54542

Browse files
committed
feat: entity links on threats
1 parent 926d91f commit 4e54542

File tree

9 files changed

+102
-7
lines changed

9 files changed

+102
-7
lines changed

valentine/lib/valentine/composer.ex

+9-1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ defmodule Valentine.Composer do
196196
from(t in Threat, where: t.workspace_id == ^workspace_id)
197197
|> list_threats_with_enum_filters(enum_filters)
198198
|> order_by([t], desc: t.numeric_id)
199+
|> preload([:assumptions, :mitigations])
199200
|> Repo.all()
200201
end
201202

@@ -213,7 +214,14 @@ defmodule Valentine.Composer do
213214
** (Ecto.NoResultsError)
214215
215216
"""
216-
def get_threat!(id), do: Repo.get!(Threat, id)
217+
def get_threat!(id, _preload \\ nil)
218+
219+
def get_threat!(id, preload) when is_list(preload) do
220+
Repo.get!(Threat, id)
221+
|> Repo.preload(preload)
222+
end
223+
224+
def get_threat!(id, preload) when is_nil(preload), do: Repo.get!(Threat, id)
217225

218226
@doc """
219227
Creates a threat.

valentine/lib/valentine_web/live/workspace_live/assumption/index.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ defmodule ValentineWeb.WorkspaceLive.Assumption.Index do
4848

4949
defp apply_action(socket, :mitigations, %{"id" => id}) do
5050
socket
51-
|> assign(:page_title, gettext("Link mitigations to mitigation"))
51+
|> assign(:page_title, gettext("Link mitigations to assumption"))
5252
|> assign(:mitigations, socket.assigns.workspace.mitigations)
5353
|> assign(:assumption, Composer.get_assumption!(id, [:mitigations]))
5454
end

valentine/lib/valentine_web/live/workspace_live/components/dropdown_select_component.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ defmodule ValentineWeb.WorkspaceLive.Components.DropdownSelectComponent do
8585

8686
{:noreply,
8787
socket
88-
|> assign(show_dropdown: false)}
88+
|> assign(show_dropdown: true)}
8989
end
9090

9191
def handle_event("toggle_dropdown", _, socket) do

valentine/lib/valentine_web/live/workspace_live/components/threat_component.ex

+25
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,28 @@ defmodule ValentineWeb.WorkspaceLive.Components.ThreatComponent do
5151
]}
5252
/>
5353
<div class="float-right">
54+
<.button
55+
is_icon_button
56+
aria-label="Linked assumptions"
57+
phx-click={
58+
JS.patch(~p"/workspaces/#{@threat.workspace_id}/threats/#{@threat.id}/assumptions")
59+
}
60+
id={"linked-threat-assumptions-#{@threat.id}"}
61+
>
62+
<.octicon name="discussion-closed-16" />
63+
<.counter>{assoc_length(@threat.assumptions)}</.counter>
64+
</.button>
65+
<.button
66+
is_icon_button
67+
aria-label="Linked mitigations"
68+
phx-click={
69+
JS.patch(~p"/workspaces/#{@threat.workspace_id}/threats/#{@threat.id}/mitigations")
70+
}
71+
id={"linked-threat-mitigations-#{@threat.id}"}
72+
>
73+
<.octicon name="check-circle-16" />
74+
<.counter>{assoc_length(@threat.mitigations)}</.counter>
75+
</.button>
5476
<.button
5577
is_icon_button
5678
aria-label="Edit"
@@ -224,4 +246,7 @@ defmodule ValentineWeb.WorkspaceLive.Components.ThreatComponent do
224246
def handle_event("update_comments", %{"comments" => comments}, socket) do
225247
{:noreply, assign(socket, :threat, %{socket.assigns.threat | comments: comments})}
226248
end
249+
250+
defp assoc_length(l) when is_list(l), do: length(l)
251+
defp assoc_length(_), do: 0
227252
end

valentine/lib/valentine_web/live/workspace_live/threat/index.ex

+29-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ defmodule ValentineWeb.WorkspaceLive.Threat.Index do
66

77
@impl true
88
def mount(%{"workspace_id" => workspace_id} = _params, _session, socket) do
9-
workspace = Composer.get_workspace!(workspace_id)
9+
workspace = Composer.get_workspace!(workspace_id, [:assumptions, :mitigations])
1010
ValentineWeb.Endpoint.subscribe("workspace_" <> workspace.id)
1111

1212
{:ok,
1313
socket
1414
|> assign(:workspace_id, workspace_id)
15+
|> assign(:workspace, workspace)
1516
|> assign(:filters, %{})
1617
|> assign(:threats, Composer.list_threats_by_workspace(workspace.id, %{}))}
1718
end
@@ -21,12 +22,26 @@ defmodule ValentineWeb.WorkspaceLive.Threat.Index do
2122
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
2223
end
2324

25+
defp apply_action(socket, :assumptions, %{"id" => id}) do
26+
socket
27+
|> assign(:page_title, gettext("Link assumptions to threat"))
28+
|> assign(:assumptions, socket.assigns.workspace.assumptions)
29+
|> assign(:threat, Composer.get_threat!(id, [:assumptions]))
30+
end
31+
2432
defp apply_action(socket, :index, %{"workspace_id" => workspace_id} = _params) do
2533
socket
2634
|> assign(:page_title, gettext("Listing threats"))
2735
|> assign(:workspace_id, workspace_id)
2836
end
2937

38+
defp apply_action(socket, :mitigations, %{"id" => id}) do
39+
socket
40+
|> assign(:page_title, gettext("Link mitigations to threat"))
41+
|> assign(:mitigations, socket.assigns.workspace.mitigations)
42+
|> assign(:threat, Composer.get_threat!(id, [:mitigations]))
43+
end
44+
3045
@impl true
3146
def handle_event("delete", %{"id" => id}, socket) do
3247
case Composer.get_threat!(id) do
@@ -83,6 +98,19 @@ defmodule ValentineWeb.WorkspaceLive.Threat.Index do
8398
}
8499
end
85100

101+
@impl true
102+
def handle_info(
103+
{_, {:saved, _threat}},
104+
socket
105+
) do
106+
{:noreply,
107+
assign(
108+
socket,
109+
:threats,
110+
Composer.list_threats_by_workspace(socket.assigns.workspace_id, socket.assigns.filters)
111+
)}
112+
end
113+
86114
@impl true
87115
def handle_info(%{topic: "workspace_" <> workspace_id}, socket) do
88116
{:noreply,

valentine/lib/valentine_web/live/workspace_live/threat/index.html.heex

+26
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,29 @@
7171
/>
7272
</:row>
7373
</.box>
74+
75+
<.live_component
76+
:if={@live_action in [:assumptions]}
77+
module={ValentineWeb.WorkspaceLive.Components.EntityLinkerComponent}
78+
id={@threat.id}
79+
source_entity_type={:threat}
80+
target_entity_type={:assumptions}
81+
entity={@threat}
82+
linkable_entities={@assumptions}
83+
linked_entities={@threat.assumptions}
84+
workspace_id={@workspace_id}
85+
patch={~p"/workspaces/#{@workspace_id}/threats"}
86+
/>
87+
88+
<.live_component
89+
:if={@live_action in [:mitigations]}
90+
module={ValentineWeb.WorkspaceLive.Components.EntityLinkerComponent}
91+
id={@threat.id}
92+
source_entity_type={:threat}
93+
target_entity_type={:mitigations}
94+
entity={@threat}
95+
linkable_entities={@mitigations}
96+
linked_entities={@threat.mitigations}
97+
workspace_id={@workspace_id}
98+
patch={~p"/workspaces/#{@workspace_id}/threats"}
99+
/>

valentine/lib/valentine_web/router.ex

+8
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ defmodule ValentineWeb.Router do
104104
live "/workspaces/:workspace_id/threats/new", WorkspaceLive.Threat.Show, :new
105105
live "/workspaces/:workspace_id/threats/:id", WorkspaceLive.Threat.Show, :edit
106106

107+
live "/workspaces/:workspace_id/threats/:id/assumptions",
108+
WorkspaceLive.Threat.Index,
109+
:assumptions
110+
111+
live "/workspaces/:workspace_id/threats/:id/mitigations",
112+
WorkspaceLive.Threat.Index,
113+
:mitigations
114+
107115
live "/workspaces/:workspace_id/threat_model", WorkspaceLive.ThreatModel.Index, :index
108116
live "/workspaces/:workspace_id/threat_model/pdf", WorkspaceLive.ThreatModel.Index, :pdf
109117

valentine/test/valentine/composer_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ defmodule Valentine.ComposerTest do
101101

102102
test "list_threats_by_workspace/2 returns all threats for a workspace" do
103103
threat = threat_fixture()
104-
assert Composer.list_threats_by_workspace(threat.workspace_id) == [threat]
104+
assert hd(Composer.list_threats_by_workspace(threat.workspace_id)).id == threat.id
105105
end
106106

107107
test "list_threats_by_workspace/2 returns all threats for a workspace adnd not other workspaces" do

valentine/test/valentine_web/live/components/dropdown_select_component_test.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ defmodule ValentineWeb.WorkspaceLive.Components.DropdownSelectComponentTest do
111111
{:noreply, socket} =
112112
DropdownSelectComponent.handle_event("select_item", %{"id" => 3}, socket)
113113

114-
assert socket.assigns.show_dropdown == false
114+
assert socket.assigns.show_dropdown == true
115115
end
116116

117117
test "selects the item when a target is defined" do
@@ -131,7 +131,7 @@ defmodule ValentineWeb.WorkspaceLive.Components.DropdownSelectComponentTest do
131131
{:noreply, socket} =
132132
DropdownSelectComponent.handle_event("select_item", %{"id" => 3}, socket)
133133

134-
assert socket.assigns.show_dropdown == false
134+
assert socket.assigns.show_dropdown == true
135135
end
136136
end
137137

0 commit comments

Comments
 (0)