Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activity log endpoint #2739

Merged
merged 13 commits into from
Jul 4, 2024
9 changes: 7 additions & 2 deletions lib/trento/activity_log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ defmodule Trento.ActivityLog do
require Logger

require Trento.ActivityLog.RetentionPeriodUnit, as: RetentionPeriodUnit

alias Trento.ActivityLog.ActivityLog
alias Trento.ActivityLog.Settings

alias Trento.Repo

@spec get_settings() ::
Expand Down Expand Up @@ -38,6 +37,12 @@ defmodule Trento.ActivityLog do
end
end

@spec list_activity_log() :: list(ActivityLog.t())
def list_activity_log do
# This will be made filterable/paginatable in a later PR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Avoid comments on future implementations

Suggested change
# This will be made filterable/paginatable in a later PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intention here was to motivate/justify the use of Repo.all in the following line, which is not very usual. In a way this implementation is partial and I was only drawing attention to this fact.

Repo.all(ActivityLog)
end

defp log_error({:error, _} = error, message) do
Logger.error("#{message}: #{inspect(error)}")
error
Expand Down
2 changes: 2 additions & 0 deletions lib/trento/activity_logging/activity_log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Trento.ActivityLog.ActivityLog do
use Ecto.Schema
import Ecto.Changeset

@type t() :: %__MODULE__{}

@primary_key {:id, :binary_id, autogenerate: true}
schema "activity_logs" do
field :type, :string
Expand Down
25 changes: 25 additions & 0 deletions lib/trento_web/controllers/v1/activity_log_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule TrentoWeb.V1.ActivityLogController do
use TrentoWeb, :controller
use OpenApiSpex.ControllerSpecs

alias Trento.ActivityLog
alias TrentoWeb.OpenApi.V1.Schema

plug OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true
action_fallback TrentoWeb.FallbackController

operation :get_activity_log,
summary: "Fetches the Activity Log entries.",
tags: ["Platform"],
responses: [
ok: {"Activity Log settings fetched successfully", "application/json", Schema.ActivityLog}
]

def get_activity_log(conn, _) do
with activity_log_entries <- ActivityLog.list_activity_log() do
render(conn, "activity_log.json", %{
activity_log: activity_log_entries
})
end
end
end
35 changes: 35 additions & 0 deletions lib/trento_web/openapi/v1/schema/activity_log.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule TrentoWeb.OpenApi.V1.Schema.ActivityLog do
@moduledoc false

require OpenApiSpex
alias OpenApiSpex.Schema

OpenApiSpex.schema(%{
title: "ActivityLog",
description: "Activity Log for the current installation.",
type: :array,
items: %Schema{
title: "ActivityLogEntry",
type: :object,
gagandeepb marked this conversation as resolved.
Show resolved Hide resolved
additionalProperties: false,
properties: %{
type: %Schema{
type: :string,
description: "Type of Activity Log entry."
},
actor: %Schema{
type: :string,
description: "Actor causing an Activity Log entry. E.g. System or a specific user."
},
metadata: %Schema{
type: :object
},
occurred_on: %Schema{
type: :string,
description: "Timestamp upon Activity Log entry insertion."
}
},
required: [:type, :actor, :metadata, :occurred_on]
}
})
end
2 changes: 2 additions & 0 deletions lib/trento_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ defmodule TrentoWeb.Router do

get "/about", AboutController, :info

get "/activity_log", ActivityLogController, :get_activity_log
gagandeepb marked this conversation as resolved.
Show resolved Hide resolved

get "/installation/api-key", InstallationController, :get_api_key

get "/hosts", HostController, :list
Expand Down
17 changes: 17 additions & 0 deletions lib/trento_web/views/v1/activity_log_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule TrentoWeb.V1.ActivityLogView do
use TrentoWeb, :view

def render("activity_log.json", %{activity_log: entries}) do
render_many(entries, __MODULE__, "activity_log_entry.json", as: :entries)
end

def render("activity_log_entry.json", %{entries: entry}) do
%{
type: entry.type,
actor: entry.actor,
metadata: entry.metadata,
# Time of occurrence approximated by time of insertion in DB.
occurred_on: entry.inserted_at
}
end
end
9 changes: 9 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ defmodule Trento.Factory do
InstallationSettings
}

alias Trento.ActivityLog.ActivityLog, as: ActivityLogEntry
alias Trento.ActivityLog.RetentionTime
alias Trento.ActivityLog.Settings, as: ActivityLogSettings

Expand Down Expand Up @@ -1025,6 +1026,14 @@ defmodule Trento.Factory do
}
end

def activity_log_entry_factory do
%ActivityLogEntry{
type: Faker.Pokemon.name(),
actor: Enum.random(["user", "system"]),
metadata: %{}
}
end

def user_factory do
password = Faker.Pokemon.name()

Expand Down
43 changes: 43 additions & 0 deletions test/trento_web/controllers/v1/activity_log_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule TrentoWeb.V1.ActivityLogControllerTest do
use TrentoWeb.ConnCase, async: true

import Trento.Factory
import OpenApiSpex.TestAssertions

alias TrentoWeb.OpenApi.V1.ApiSpec

describe "ActivityLogController" do
setup do
%{api_spec: ApiSpec.spec()}
end

test "should return activity logs after inserting a few entries.", %{
conn: conn,
api_spec: api_spec
} do
insert(:activity_log_entry)
insert(:activity_log_entry)

resp =
conn
|> get("/api/v1/activity_log")
|> json_response(200)

assert length(resp) == 2
assert_schema(resp, "ActivityLog", api_spec)
end

test "should return valid response (empty list) if no activity logs entries exist", %{
conn: conn,
api_spec: api_spec
} do
resp =
conn
|> get("/api/v1/activity_log")
|> json_response(200)

assert resp == []
assert_schema(resp, "ActivityLog", api_spec)
end
end
end