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

2601 allow non user audit actors #2638

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

rorymckinley
Copy link
Collaborator

Description

This allows audit events to be created with non-user actors. This is in preparation for the auditing of snapshots, where the actor may be a ProjectRepoConnection or a Trigger.

There are a fair number of changes in this PR, but they mostly consist of:

  • Adding extra test coverage
  • Swapping out a user id for the user struct.

I have tried to group related changes in commits to make reviewing easier.

#2601

Validation steps

As the intent of this change is to have everything remain unchanged, I would suggest the following:

  • On main make some retention period changes for a project.
  • Take a screenshot of the Audit UI.
  • Switch to this branch.
  • Make similar changes to the changes on main
  • Compare the audit screen to your screenshot.

AI Usage

Please disclose how you've used AI in this work (it's cool, we just want to know!):

  • Code generation (copilot but not intellisense)
  • Learning or fact checking
  • Strategy / design
  • Optimisation / refactoring
  • Translation / spellchecking / doc gen
  • Other
  • I have not used AI

You can read more details in our Responsible AI Policy

Pre-submission checklist

  • I have performed a self-review of my code.
  • I have implemented and tested all related authorization policies. (e.g., :owner, :admin, :editor, :viewer)
  • I have updated the changelog.
  • I have ticked a box in "AI usage" in this PR

@@ -12,7 +12,16 @@ defmodule Lightning.Auditing do
from(a in Audit,
left_join: u in User,
on: [id: a.actor_id],
select_merge: %{actor: u},
select_merge: %{
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The next iteration of this query is likely to be a Union - some experimentation with a union and a select merge that maps to a Struct produced issues. So, for now, I am going with a dumber version that will hopefully be more acceptable once the union is implemented.

Copy link

codecov bot commented Nov 6, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.88%. Comparing base (d67c136) to head (023abfb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2638   +/-   ##
=======================================
  Coverage   90.87%   90.88%           
=======================================
  Files         320      320           
  Lines       11257    11252    -5     
=======================================
- Hits        10230    10226    -4     
+ Misses       1027     1026    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rorymckinley rorymckinley self-assigned this Nov 6, 2024
@rorymckinley rorymckinley marked this pull request as ready for review November 6, 2024 12:50
Copy link
Member

@jyeshe jyeshe left a comment

Choose a reason for hiding this comment

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

Nice abstraction Sir @rorymckinley ! Nit picking you again with pipes and I would like to propose keeping the existing virtual actor map unless we are sure that the View is very stable and unlikely to change according to multiple actor types available.

Comment on lines +125 to +126
field :actor_display_identifier, :string, virtual: true
field :actor_display_label, :string, virtual: true
Copy link
Member

@jyeshe jyeshe Nov 6, 2024

Choose a reason for hiding this comment

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

Model exists to support some view at least at some point in time (internally or externally) so I guess where you're heading to but we might end up having more flexibility if we keep/preserve the actor map and use the same trick you did but assigning to the virtual map:

import Ecto.Query; 
query = 
  from a in Lightning.Auditing.Audit, 
    left_join: u in Lightning.Accounts.User, 
    on: [id: a.actor_id], 
    select_merge: %{actor: u}
Repo.all(query) 

Then whatever change we might have on the View, for any of the three actor types, we wouldn't need to change the model again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@jyeshe My initial intent was to retain the actor map, but when I started experimenting with unions, I ran into the following error:

** (Ecto.SubQueryError) the following exception happened when compiling a subquery.

         ** (Ecto.QueryError) atoms, structs, maps, lists, tuples and sources are not allowed as map values in subquery, got: `%{actor: &1}` in query:
         
         from a0 in Lightning.Auditing.Audit,
           prefix: "public",
           left_join: u1 in Lightning.Accounts.User,
           on: u1.id == a0.actor_id,
           where: a0.actor_type == "Lightning.Accounts.User",
           union_all: (from a0 in Lightning.Auditing.Audit,
           prefix: "public",
           left_join: p1 in Lightning.VersionControl.ProjectRepoConnection,
           on: p1.id == a0.actor_id,
           where: a0.actor_type == "Lightning.VersionControl.ProjectRepoConnection",
           select: merge(a0, %{actor: p1})),
           select: merge(a0, %{actor: u1})

There are ways to correct this and still retain the actor map but I am not sure that the benefit outweighs the cost currently, as my understanding of the deliverable is that the primary intent is to ensure that the data is captured.

Therefore, my preference is to apply a less elegant and less flexible approach to satisfy the current view requirements so that I can maintain momentum on the capturing of audit events. I am more than willing to revisit this if (a) one of the new events demands it or (b) there is time to do so once the primary objectives have been met.

Copy link
Member

@jyeshe jyeshe Nov 7, 2024

Choose a reason for hiding this comment

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

Got it, in this case I would do just a single query with select: %{audit: a, user: u, trigger: t, provision: p}
It would allow keeping a generic model and use a single select on the SQL side.
To set the actor would be as clean as:

Enum.map(audits, &Map.put(&1.audit, :actor, &1.user || &1.trigger || &1.provision))

Copy link
Member

Choose a reason for hiding this comment

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

But again if we are sure of what we want on the presentation layer, if it's a stable view, I am fine with the only two virtuals.

@@ -173,7 +178,7 @@ defmodule Lightning.Auditing.Audit do
String.t(),
String.t(),
Ecto.UUID.t(),
Ecto.UUID.t(),
struct(),
Copy link
Member

Choose a reason for hiding this comment

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

if there are only 3 types we can enumerate them with a |:
Lightning.Accounts.User.t() | Lightning.Workflows.Trigger.t() | ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, nice - fixed. Thanks @jyeshe

changeset(
%__MODULE__{},
%{
item_type: item_type,
event: event,
item_id: item_id,
actor_id: actor_id,
actor_type: actor_struct |> extract_actor_type(),
Copy link
Member

Choose a reason for hiding this comment

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

well done getting the module type mr Rory, this is nice trick. Please remember that the |> are for chaining values and not much for simple function calls. Sometimes we do:

socket
|> assign(...)

but there is a purpose of making it easy to add other assigns. If we do piping for all function calls it produces extra work for the compiler on each compilation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @jyeshe - cleaned up.

Comment on lines 258 to 259
struct_name
|> case do
Copy link
Member

Choose a reason for hiding this comment

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

Similar to the function call above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks - cleaned up.

@@ -269,10 +269,9 @@ defmodule Lightning.Credentials do
|> Multi.insert(
:audit,
fn %{credential: credential} ->
Audit.event(
Audit.user_initiated_event(
Copy link
Member

Choose a reason for hiding this comment

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

like

@@ -24,4 +24,10 @@ defmodule Lightning.Credentials.Audit do
changes
end
end

def user_initiated_event(event, credential, changes \\ %{}) do
Copy link
Member

Choose a reason for hiding this comment

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

❤️

@rorymckinley
Copy link
Collaborator Author

Thanks for the feedback @jyeshe , I have addressed it in two follow-up commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In review
Development

Successfully merging this pull request may close these issues.

2 participants