-
Notifications
You must be signed in to change notification settings - Fork 5
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
Feature/2219 changelog #411
Closed
Closed
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
dbb5110
WIP
timohuber ac1b514
catch null values
timohuber dcaf240
add model and user to changelog
timohuber 96a87ad
add migration and repo
timohuber 6cacddf
add db insert function
timohuber bf27cd8
add get_all
timohuber 02ee78f
show changelog
timohuber 12442e3
refactor change type
timohuber eccef65
improve formatting
timohuber 54bc47f
update arrow char
timohuber e5ce85c
add htmx handlers
timohuber 2d1c400
show admin email address
timohuber 18a3720
catch equal assoc lists
timohuber e18c6c5
refactor handling of assoc lists
timohuber a75efa3
add interface
timohuber 5626305
prettify
timohuber d317615
WIP resolve mr discussion
timohuber 7efbaa0
resolve mr discussion
timohuber 32c1864
WIP
timohuber 8b9b0c9
Merge branch 'main' into feature/2219-changelog
timohuber c668e79
add OU changelog
timohuber 3dd2ccc
add ou changelog routes
timohuber 4966457
load changelog on page load
timohuber ee0c6c0
remove unused function
timohuber 8afcc88
create changelogs in separte events
timohuber 9c92668
reuse signature
timohuber 994870f
add changelog model tests
timohuber 8f348af
filter changelog
timohuber cd74e66
update filter
timohuber 467651b
assignment changelog
timohuber 4a5ef30
Merge branch 'main' into feature/2219-changelog
timohuber 1d290da
fix tests WIP
timohuber File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
open Ppx_yojson_conv_lib.Yojson_conv | ||
open Entity | ||
|
||
module Record = struct | ||
open Entity | ||
|
||
let model = Pool_message.Field.Assignment | ||
|
||
type t = | ||
{ id : Id.t | ||
[@equal fun a b -> Id.equal a b || Sihl.Configuration.is_test ()] | ||
; contact : Contact.Id.t | ||
; no_show : NoShow.t option | ||
; participated : Participated.t option | ||
; matches_filter : MatchesFilter.t | ||
; canceled_at : CanceledAt.t option | ||
; marked_as_deleted : MarkedAsDeleted.t | ||
; external_data_id : ExternalDataId.t option | ||
; reminder_manually_last_sent_at : Pool_common.Reminder.SentAt.t option | ||
; custom_fields : Custom_field.Id.t list option | ||
; created_at : Pool_common.CreatedAt.t | ||
; updated_at : Pool_common.UpdatedAt.t | ||
} | ||
[@@deriving eq, show, yojson] | ||
end | ||
|
||
let to_record | ||
({ id | ||
; contact | ||
; no_show | ||
; participated | ||
; matches_filter | ||
; canceled_at | ||
; marked_as_deleted | ||
; external_data_id | ||
; reminder_manually_last_sent_at | ||
; custom_fields | ||
; created_at | ||
; updated_at | ||
} : | ||
Entity.t) | ||
: Record.t | ||
= | ||
let custom_fields = | ||
custom_fields |> CCOption.map (CCList.map Custom_field.Public.id) | ||
in | ||
Record. | ||
{ id | ||
; contact = Contact.id contact | ||
; no_show | ||
; participated | ||
; matches_filter | ||
; canceled_at | ||
; marked_as_deleted | ||
; external_data_id | ||
; reminder_manually_last_sent_at | ||
; custom_fields | ||
; created_at | ||
; updated_at | ||
} | ||
;; | ||
|
||
include Changelog.T (Record) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
include Entity | ||
include Event | ||
|
||
module T (R : RecordSig) = struct | ||
type record = R.t | ||
|
||
let model = R.model | ||
|
||
let column_created_at = | ||
(Pool_message.Field.CreatedAt, "pool_change_log.created_at") | ||
|> Query.Column.create | ||
;; | ||
|
||
let searchable_by = [] | ||
let sortable_by = [] | ||
let filterable_by = None | ||
|
||
let default_sort = | ||
let open Query in | ||
Sort.{ column = column_created_at; order = SortOrder.Descending } | ||
;; | ||
|
||
let default_query = Query.create ~sort:default_sort () | ||
|
||
let make_changes before after : Changes.t option = | ||
let open Changes in | ||
let rec compare json_before json_after = | ||
let eq = CCString.equal in | ||
let compare_json before after = | ||
match Yojson.Safe.equal before after with | ||
| true -> None | ||
| false -> Some (Change (before, after)) | ||
in | ||
let list_to_assoc (json_list : Yojson.Safe.t list) = | ||
let rec check acc = function | ||
| [] -> Some (`Assoc acc) | ||
| `List [ key; value ] :: tl -> | ||
check (acc @ [ Yojson.Safe.to_string key, value ]) tl | ||
| _ -> None | ||
in | ||
check [] json_list | ||
in | ||
match (json_before : Yojson.Safe.t), (json_after : Yojson.Safe.t) with | ||
| `Assoc l1, `Assoc l2 -> | ||
let keys = | ||
let get = CCList.map fst in | ||
get l1 @ get l2 |> CCList.uniq ~eq | ||
in | ||
keys | ||
|> CCList.filter_map (fun key -> | ||
let assoc list = | ||
CCList.assoc_opt ~eq key list |> CCOption.value ~default:`Null | ||
in | ||
let value_before = assoc l1 in | ||
let value_after = assoc l2 in | ||
compare value_before value_after | ||
|> CCOption.map (fun value -> key, value)) | ||
|> (function | ||
| [] -> None | ||
| assoc -> Some (Assoc assoc)) | ||
(* Catching equal variants *) | ||
| `Tuple [ `String key1; val1 ], `Tuple [ `String key2; val2 ] | ||
| `List [ `String key1; val1 ], `List [ `String key2; val2 ] -> | ||
if eq key1 key2 | ||
then compare val1 val2 | ||
else Some (Change (json_before, json_after)) | ||
(* Catching equal assoc lists *) | ||
| `List l1, `List l2 -> | ||
(match list_to_assoc l1, list_to_assoc l2 with | ||
| Some assoc_before, Some assoc_after -> | ||
compare assoc_before assoc_after | ||
| _, _ -> compare_json json_before json_after) | ||
| _ -> compare_json json_before json_after | ||
in | ||
compare (R.yojson_of_t before) (R.yojson_of_t after) | ||
;; | ||
|
||
let make | ||
?(id = Id.create ()) | ||
~entity_uuid | ||
~user_uuid | ||
(before : R.t) | ||
(after : R.t) | ||
= | ||
(*** the entity_uuid could also be part of the Reord module, as a function. | ||
This would probably require us to create a Record module for each model, | ||
but provide more flexibility *) | ||
make_changes before after | ||
|> CCOption.map (fun changes -> | ||
Write. | ||
{ id | ||
; changes | ||
; model | ||
; entity_uuid | ||
; user_uuid | ||
; created_at = Pool_common.CreatedAt.create_now () | ||
}) | ||
;; | ||
|
||
let create ?(id = Id.create ()) ~entity_uuid ~user_uuid ~before ~after () = | ||
make ~id ~entity_uuid ~user_uuid before after | ||
;; | ||
|
||
let all_by_entity = Repo.find_by_model model | ||
end | ||
|
||
(*** | ||
|
||
let make_changes before after : Changes.t option = let open Changes in let rec | ||
compare json_before json_after = let eq = CCString.equal in let make_changes | ||
before after = match Yojson.Safe.equal before after with | true -> None | | ||
false -> Some (Change (before, after)) in let list_to_assoc (json_list : | ||
Yojson.Safe.t list) = let rec check acc = function | [] -> Some (`Assoc acc) | | ||
`List [ key; value ] :: tl -> check (acc @ [ Yojson.Safe.to_string key, value | ||
]) tl | _ -> None in check [] json_list in match (json_before : | ||
Yojson.Safe.t), (json_after : Yojson.Safe.t) with | `Assoc l1, `Assoc l2 -> | ||
let keys = let get = CCList.map fst in get l1 @ get l2 |> CCList.uniq ~eq in | ||
keys |> CCList.filter_map (fun key -> let assoc list = CCList.assoc_opt ~eq | ||
key list |> CCOption.value ~default:`Null in let value_before = assoc l1 in | ||
let value_after = assoc l2 in compare value_before value_after |> CCOption.map | ||
(fun value -> key, value)) |> (function | [] -> None | assoc -> Some (Assoc | ||
assoc)) (* Catching equal variants *) | `Tuple [ `String key1; val1 ], `Tuple | ||
[ `String key2; val2 ] | `List [ `String key1; val1 ], `List [ `String key2; | ||
val2 ] -> if eq key1 key2 then compare val1 val2 else Some (Change | ||
(json_before, json_after)) (* Catching equal assoc lists *) | `List l1, `List | ||
l2 -> (match list_to_assoc l1, list_to_assoc l2 with | Some assoc_before, Some | ||
assoc_after -> compare assoc_before assoc_after | None, None -> let open | ||
CCList in let open CCOption.Infix in let after = Hashtbl.create (length l2) in | ||
let () = l2 |> iteri (fun i x -> Hashtbl.add after i x) in let changes_before | ||
= l1 |> foldi (fun acc i before_json -> Hashtbl.find_opt after i >>= (fun | ||
after_json -> make_changes before_json after_json) >|= CCPair.make | ||
(CCInt.to_string i) |> function | Some change -> let () = Hashtbl.remove after | ||
i in acc @ [ change ] | None -> acc) [] in let changes_after = after |> | ||
Hashtbl.to_seq |> of_seq |> filter_map (fun (i, json) -> make_changes `Null | ||
json >|= CCPair.make (CCInt.to_string i)) in (match changes_before @ | ||
changes_after with | [] -> None | changes -> Some (Assoc changes)) | _, _ -> | ||
make_changes json_before json_after) | _ -> make_changes json_before | ||
json_after in compare (R.yojson_of_t before) (R.yojson_of_t after) ;; *) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
module Id : sig | ||
include Pool_model.Base.IdSig | ||
end | ||
|
||
module Change : sig | ||
type t = Yojson.Safe.t * Yojson.Safe.t | ||
|
||
val equal : t -> t -> bool | ||
val pp : Format.formatter -> t -> unit | ||
val show : t -> string | ||
end | ||
|
||
module Changes : sig | ||
type t = | ||
| Assoc of (string * t) list | ||
| Change of Change.t | ||
end | ||
|
||
type t = | ||
{ id : Id.t | ||
; changes : Changes.t | ||
; model : Pool_message.Field.t | ||
; entity_uuid : Pool_common.Id.t | ||
; user_uuid : Pool_common.Id.t | ||
; user_email : Pool_user.EmailAddress.t | ||
; created_at : Pool_common.CreatedAt.t | ||
} | ||
|
||
val equal : t -> t -> bool | ||
val show : t -> string | ||
val pp : Format.formatter -> t -> unit | ||
|
||
module Write : sig | ||
type t = | ||
{ id : Id.t | ||
; changes : Changes.t | ||
; model : Pool_message.Field.t | ||
; entity_uuid : Pool_common.Id.t | ||
; user_uuid : Pool_common.Id.t | ||
; created_at : Pool_common.CreatedAt.t | ||
} | ||
|
||
val equal : t -> t -> bool | ||
val show : t -> string | ||
val pp : Format.formatter -> t -> unit | ||
end | ||
|
||
module type RecordSig = sig | ||
type t | ||
|
||
val model : Pool_message.Field.t | ||
val yojson_of_t : t -> Yojson.Safe.t | ||
end | ||
|
||
module type TSig = sig | ||
type record | ||
|
||
val model : Pool_message.Field.t | ||
val default_query : Query.t | ||
val filterable_by : Query.Filter.human option | ||
val searchable_by : Query.Column.t list | ||
val sortable_by : Query.Column.t list | ||
|
||
val create | ||
: ?id:Id.t | ||
-> entity_uuid:Pool_common.Id.t | ||
-> user_uuid:Pool_common.Id.t | ||
-> before:record | ||
-> after:record | ||
-> unit | ||
-> Write.t option | ||
|
||
val all_by_entity | ||
: ?query:Query.t | ||
-> Database.Label.t | ||
-> Pool_common.Id.t | ||
-> (t list * Query.t) Lwt.t | ||
end | ||
|
||
module T : functor (R : RecordSig) -> sig | ||
include TSig with type record = R.t | ||
end | ||
|
||
type event = Created of Write.t | ||
|
||
val equal_event : event -> event -> bool | ||
val pp_event : Format.formatter -> event -> unit | ||
val show_event : event -> string | ||
val handle_event : Database.Label.t -> event -> unit Lwt.t | ||
val created : Write.t -> event |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
(library | ||
(name changelog) | ||
(libraries pool_common pool_user pool_message ptime sihl utils query) | ||
(preprocess | ||
(pps | ||
lwt_ppx | ||
ppx_deriving.enum | ||
ppx_deriving.eq | ||
ppx_deriving.ord | ||
ppx_deriving.show | ||
ppx_fields_conv | ||
ppx_sexp_conv | ||
ppx_variants_conv | ||
ppx_yojson_conv))) | ||
|
||
(include_subdirs unqualified) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is still a WIP note 😄
Still not sure if it is more convenient to use the
Entity
module (https://github.com/uzh/z-pool-tool/blob/feature/2219-changelog/pool/app/pool_location/version_history.ml) or to create e separateRecord
module.