Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thatportugueseguy committed Dec 12, 2024
1 parent 308a87d commit 9cc9923
Show file tree
Hide file tree
Showing 29 changed files with 875 additions and 1,032 deletions.
47 changes: 40 additions & 7 deletions lib/action.ml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
let partition_status (ctx : Context.t) (n : status_notification) =
let repo = n.repository in
let cfg = Context.find_repo_config_exn ctx repo.url in
let pipeline = n.context in
let context = n.context in
let rules = cfg.status_rules.rules in
let repo_state = State.find_or_add_repo ctx.state repo.url in
let action_on_match (branches : branch list) ~notify_channels ~notify_dm =
Expand Down Expand Up @@ -187,13 +187,13 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
in
(* only notify the failed builds channels for full failed builds with new failed steps on the main branch *)
let notify_failed_builds_channel =
Util.Build.is_failed_build n
is_main_branch
&& Util.Build.is_failed_build n
&& Util.Build.new_failed_steps n repo_state <> []
&& is_main_branch
&& Option.map_default
(fun allowed_pipelines ->
List.exists
(fun { failed_builds_channel; name } -> name = n.context && Option.is_some failed_builds_channel)
(fun { failed_builds_channel; name } -> name = context && Option.is_some failed_builds_channel)
allowed_pipelines)
false cfg.status_rules.allowed_pipelines
in
Expand All @@ -205,7 +205,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
let chans =
List.find_map
(fun ({ name; failed_builds_channel } : Config_t.pipeline) ->
match String.equal name n.context, failed_builds_channel with
match String.equal name context, failed_builds_channel with
| true, Some failed_builds_channel -> Some (Slack_channel.to_any failed_builds_channel :: chans)
| _ -> None)
allowed_pipelines
Expand All @@ -215,13 +215,46 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
Lwt.return (direct_message @ chans)
in
let%lwt recipients =
if Context.is_pipeline_allowed ctx repo.url ~pipeline then begin
if Context.is_pipeline_allowed ctx repo.url ~context then begin
match Rule.Status.match_rules ~rules n with
| Some (Ignore, _, _) | None -> Lwt.return []
| Some (Allow, notify_channels, notify_dm) -> action_on_match n.branches ~notify_channels ~notify_dm
| Some (Allow_once, notify_channels, notify_dm) ->
let branches =
match n.target_url with
| None -> n.branches
| Some build_url ->
let pipeline_name =
(* We only need to track messages for the base pipeline, not the steps *)
match Util.Build.parse_context ~context ~build_url with
| Ok { Util.Build.pipeline_name; _ } -> pipeline_name
| Error _ -> context
in
(match StringMap.find_opt pipeline_name repo_state.pipeline_statuses with
| None ->
(* this is the first notification for a pipeline, so no need to filter branches *)
n.branches
| Some branch_statuses ->
let has_same_status (branch : branch) =
match StringMap.find_opt branch.name branch_statuses with
| Some build_statuses ->
let current = Util.Build.get_build_number_exn ~context ~build_url in
let previous_builds = StringMap.filter (fun build_num _ -> build_num < current) build_statuses in
(match StringMap.is_empty previous_builds with
| true ->
(* if we have no previous builds, it means they were successful and cleaned from state *)
n.state = Github_t.Success
| false ->
let _, previous_build = StringMap.max_binding previous_builds in
previous_build.status = n.state)
| _ ->
(* if we don't have any builds for this branch yet, it's the first notification for this pipeline *)
false
in
List.filter (Fun.negate has_same_status) n.branches)
in
let notify_dm = notify_dm && not (State.mem_repo_pipeline_commits ctx.state n) in
action_on_match n.branches ~notify_channels ~notify_dm
action_on_match branches ~notify_channels ~notify_dm
end
else Lwt.return []
in
Expand Down
4 changes: 2 additions & 2 deletions lib/context.ml
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ let hook_of_channel ctx channel_name =
(** [is_pipeline_allowed ctx repo_url ~pipeline] returns [true] if [status_rules]
doesn't define a whitelist of allowed pipelines in the config of [repo_url],
or if the list contains [pipeline]; returns [false] otherwise. *)
let is_pipeline_allowed ctx repo_url ~pipeline =
let is_pipeline_allowed ctx repo_url ~context =
match find_repo_config ctx repo_url with
| None -> true
| Some config ->
match config.status_rules.allowed_pipelines with
| Some allowed_pipelines
when not @@ List.exists (fun (p : Config_t.pipeline) -> String.equal p.name pipeline) allowed_pipelines ->
when not @@ List.exists (fun (p : Config_t.pipeline) -> String.equal p.name context) allowed_pipelines ->
false
| _ -> true

Expand Down
4 changes: 2 additions & 2 deletions lib/slack.ml
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,14 @@ let generate_status_notification ~(ctx : Context.t) ?slack_user_id (cfg : Config
&& Option.map_default Slack_channel.(equal channel $ to_any) false failed_builds_channel)
pipelines
in
match Util.Build.is_failed_build notification && is_failed_builds_channel with
match Build.is_failed_build notification && is_failed_builds_channel with
| false -> []
| true ->
let repo_state = State.find_or_add_repo ctx.state repository.url in
let pipeline = notification.context in
let slack_step_link (s : State_t.failed_step) =
let step = Stre.drop_prefix s.name (pipeline ^ "/") in
Printf.sprintf "<%s|%s> " (Option.default "" s.build_link) step
Printf.sprintf "<%s|%s> " s.build_url step
in
(match Build.new_failed_steps notification repo_state with
| [] -> []
Expand Down
4 changes: 2 additions & 2 deletions lib/state.atd
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ type ci_commit = {

type failed_step = {
name: string;
build_link: string option;
build_url: string;
}

type build_status = {
status: status_state;
build_number: string;
build_link: string option;
build_url: string;
commit: ci_commit;
is_finished: bool;
failed_steps: failed_step list;
Expand Down
175 changes: 88 additions & 87 deletions lib/state.ml
Original file line number Diff line number Diff line change
Expand Up @@ -46,102 +46,103 @@ let update_builds_in_branches ~branches ?(default_builds_map = StringMap.empty)
Some (List.fold_left (fun m (key, data) -> StringMap.add key data m) current_statuses updated_statuses)

let set_repo_pipeline_status { state } (n : Github_t.status_notification) =
let target_url = Option.get n.target_url in
let context = n.context in
let { Util.Build.is_pipeline_step; pipeline_name }, build_number =
match
( Util.Build.parse_context ~context ~build_url:target_url,
Util.Build.get_build_number ~context ~build_url:target_url )
with
| Ok context, Ok build_number -> context, build_number
| Error msg, _ | _, Error msg -> failwith msg
in
let is_finished = (not is_pipeline_step) && (n.state = Success || n.state = Failure || n.state = Error) in
let finished_at =
match is_finished with
| true -> Some n.updated_at
| false -> None
in
(* Even if this is an initial build state, we can't just set an "empty" state because we don't know
the order we will get the status notifications in. *)
let init_build_state =
let commit =
{ State_t.sha = n.sha; author = n.commit.commit.author.email; commit_message = n.commit.commit.message }
in
let failed_steps =
match is_pipeline_step, n.state with
| true, Failure -> [ { State_t.name = n.context; build_link = n.target_url } ]
| _ -> []
match n.target_url with
| None ->
(* if we don't have a target_url value, we don't have a build number and cant't track build state *)
()
| Some build_url ->
let context = n.context in
let { Util.Build.is_pipeline_step; pipeline_name } = Util.Build.parse_context_exn ~context ~build_url in
let build_number = Util.Build.get_build_number_exn ~context ~build_url in
let is_finished = (not is_pipeline_step) && (n.state = Success || n.state = Failure || n.state = Error) in
let finished_at =
match is_finished with
| true -> Some n.updated_at
| false -> None
in
{
State_t.status = n.state;
build_number;
build_link = n.target_url;
commit;
is_finished;
failed_steps;
created_at = n.updated_at;
finished_at;
}
in
let update_build_status builds_map build_number =
match StringMap.find_opt build_number builds_map with
| Some ({ failed_steps; _ } as current_build_status : State_t.build_status) ->
(* Even if this is an initial build state, we can't just set an "empty" state because we don't know
the order we will get the status notifications in. *)
let init_build_state =
let commit =
{ State_t.sha = n.sha; author = n.commit.commit.author.email; commit_message = n.commit.commit.message }
in
let failed_steps =
match is_pipeline_step, n.state with
| true, Failure -> { State_t.name = n.context; build_link = n.target_url } :: failed_steps
| _ -> failed_steps
| true, Failure -> [ { State_t.name = n.context; build_url } ]
| _ -> []
in
{ current_build_status with status = n.state; is_finished; finished_at; failed_steps }
| None -> init_build_state
in
let update_pipeline_status =
update_builds_in_branches ~branches:n.branches
~default_builds_map:(StringMap.singleton build_number init_build_state) ~f:(fun builds_map ->
let updated_status = update_build_status builds_map build_number in
StringMap.add build_number updated_status builds_map)
in
let rm_successful_build = update_builds_in_branches ~branches:n.branches ~f:(StringMap.remove build_number) in
let rm_successful_step =
update_builds_in_branches ~branches:n.branches ~f:(fun builds_map ->
StringMap.mapi
(fun build_number' (build_status : State_t.build_status) ->
let failed_steps =
List.filter
(* remove the fixed step from previous finished builds *)
(fun (s : State_t.failed_step) ->
not (build_number' < build_number && s.name = n.context && build_status.is_finished))
build_status.failed_steps
in
{ build_status with failed_steps })
builds_map
|> StringMap.filter (fun build_number' build_status ->
(* Remove old builds without failed steps *)
match build_status.State_t.failed_steps with
| [] when build_number' < build_number && build_status.is_finished -> false
| _ -> true))
in
let repo_state = find_or_add_repo' state n.repository.url in
match n.state with
| Success ->
(* If a build step is successful, we remove it from the failed steps list of past builds.
If old builds have no more failed steps, we remove them.
If the whole build is successful, we remove it from state to avoid the state file on disk growing too much.
*)
(match is_pipeline_step with
| true ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name rm_successful_step repo_state.pipeline_statuses
| false ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name rm_successful_build repo_state.pipeline_statuses)
| _ ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name update_pipeline_status repo_state.pipeline_statuses
{
State_t.status = n.state;
build_number;
build_url;
commit;
is_finished;
failed_steps;
created_at = n.updated_at;
finished_at;
}
in
let update_build_status builds_map build_number =
match StringMap.find_opt build_number builds_map with
| Some ({ failed_steps; _ } as current_build_status : State_t.build_status) ->
let failed_steps =
match is_pipeline_step, n.state with
| true, Failure -> { State_t.name = n.context; build_url } :: failed_steps
| _ -> failed_steps
in
{ current_build_status with status = n.state; is_finished; finished_at; failed_steps }
| None -> init_build_state
in
let update_pipeline_status =
update_builds_in_branches ~branches:n.branches
~default_builds_map:(StringMap.singleton build_number init_build_state) ~f:(fun builds_map ->
let updated_status = update_build_status builds_map build_number in
StringMap.add build_number updated_status builds_map)
in
let rm_successful_build =
update_builds_in_branches ~branches:n.branches ~f:(fun builds_map ->
StringMap.remove build_number builds_map
|> StringMap.filter (fun build_number' build_status ->
(* Remove old builds without failed steps because they were fixed and cleaned from state *)
match build_status.State_t.failed_steps with
| [] when build_number' < build_number && build_status.is_finished -> false
| _ -> true))
in
let rm_successful_step =
update_builds_in_branches ~branches:n.branches ~f:(fun builds_map ->
StringMap.mapi
(fun build_number' (build_status : State_t.build_status) ->
let failed_steps =
List.filter
(* remove the fixed step from previous finished builds *)
(fun (s : State_t.failed_step) ->
not (build_number' < build_number && s.name = n.context && build_status.is_finished))
build_status.failed_steps
in
{ build_status with failed_steps })
builds_map)
in
let repo_state = find_or_add_repo' state n.repository.url in
(match n.state with
| Success ->
(* If a build step is successful, we remove it from the failed steps list of past builds.
If old builds have no more failed steps, we remove them.
If the whole build is successful, we remove it from state to avoid the state file on disk growing too much.
*)
(match is_pipeline_step with
| true ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name rm_successful_step repo_state.pipeline_statuses
| false ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name rm_successful_build repo_state.pipeline_statuses)
| Error | Failure | Pending ->
repo_state.pipeline_statuses <- StringMap.update pipeline_name update_pipeline_status repo_state.pipeline_statuses)

let set_repo_pipeline_commit { state } (n : Github_t.status_notification) =
let rotation_threshold = 1000 in
let repo_state = find_or_add_repo' state n.repository.url in
let pipeline =
(* We only need to track messages for the base pipeline, not the steps *)
match Util.Build.parse_context ~context:n.context ~build_url:(Option.get n.target_url) with
match Util.Build.parse_context ~context:n.context ~build_url:(Option.default "" n.target_url) with
| Ok { Util.Build.pipeline_name; _ } -> pipeline_name
| Error _ -> n.context
in
Expand All @@ -156,7 +157,7 @@ let set_repo_pipeline_commit { state } (n : Github_t.status_notification) =
let mem_repo_pipeline_commits { state } (n : Github_t.status_notification) =
let pipeline =
(* We only need to track messages for the base pipeline, not the steps *)
match Util.Build.parse_context ~context:n.context ~build_url:(Option.get n.target_url) with
match Util.Build.parse_context ~context:n.context ~build_url:(Option.default "" n.target_url) with
| Ok { Util.Build.pipeline_name; _ } -> pipeline_name
| Error _ -> n.context
in
Expand Down
Loading

0 comments on commit 9cc9923

Please sign in to comment.