Skip to content

Commit 0f80e21

Browse files
committed
Merge branch 'save-passing-bridge-notifications-to-db' into 'develop'
Add option for user to save bridge signals. See merge request programaker-project/programaker-core!200
2 parents 5ed61b2 + 193a192 commit 0f80e21

27 files changed

+889
-309
lines changed

backend/apps/automate_bot_engine/src/automate_bot_engine_triggers.erl

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -112,24 +112,24 @@ get_expected_action_from_trigger(Trigger, _Permissions, ProgramId) ->
112112
%% If any value is OK
113113
-spec trigger_thread(#program_trigger{}, {atom(), any()}, #program_state{}) -> 'false' | {'true', #program_thread{}}.
114114
trigger_thread(#program_trigger{ condition=#{ ?TYPE := ?WAIT_FOR_MONITOR_COMMAND
115-
, ?ARGUMENTS := MonitorArgs=#{ ?MONITOR_ID := #{ ?FROM_SERVICE := ServiceId }
116-
, ?MONITOR_EXPECTED_VALUE := ?MONITOR_ANY_VALUE
117-
}
118-
}
119-
, subprogram=Program
120-
},
115+
, ?ARGUMENTS := MonitorArgs=#{ ?MONITOR_ID := #{ ?FROM_SERVICE := ServiceId }
116+
, ?MONITOR_EXPECTED_VALUE := ?MONITOR_ANY_VALUE
117+
}
118+
}
119+
, subprogram=Program
120+
},
121121
{ ?TRIGGERED_BY_MONITOR, {MonitorId, FullMessage=#{ ?CHANNEL_MESSAGE_CONTENT := MessageContent, <<"service_id">> := ServiceId }} },
122122
#program_state{program_id=ProgramId}) ->
123123
trigger_thread_with_matching_message(Program, ProgramId, MonitorId, MonitorArgs, MessageContent, FullMessage);
124124

125125

126126
trigger_thread(#program_trigger{ condition=#{ ?TYPE := ?WAIT_FOR_MONITOR_COMMAND
127-
, ?ARGUMENTS := MonitorArgs=#{ ?MONITOR_ID := MonitorId
128-
, ?MONITOR_EXPECTED_VALUE := ?MONITOR_ANY_VALUE
129-
}
130-
}
131-
, subprogram=Program
132-
},
127+
, ?ARGUMENTS := MonitorArgs=#{ ?MONITOR_ID := MonitorId
128+
, ?MONITOR_EXPECTED_VALUE := ?MONITOR_ANY_VALUE
129+
}
130+
}
131+
, subprogram=Program
132+
},
133133
{ ?TRIGGERED_BY_MONITOR, {MonitorId, FullMessage=#{ ?CHANNEL_MESSAGE_CONTENT := MessageContent }} },
134134
#program_state{program_id=ProgramId}) ->
135135
trigger_thread_with_matching_message(Program, ProgramId, MonitorId, MonitorArgs, MessageContent, FullMessage);
@@ -173,8 +173,8 @@ trigger_thread(#program_trigger{ condition= Op=#{ ?TYPE := ?WAIT_FOR_MONITOR_COM
173173

174174
%% Bridge channel
175175
trigger_thread(#program_trigger{ condition= Op=#{ ?TYPE := <<"services.", MonitorPath/binary>>
176-
, ?ARGUMENTS := MonitorArgs
177-
}
176+
, ?ARGUMENTS := MonitorArgs
177+
}
178178
, subprogram=Program
179179
},
180180
{ ?TRIGGERED_BY_MONITOR, { MonitorId
@@ -212,14 +212,14 @@ trigger_thread(#program_trigger{ condition= Op=#{ ?TYPE := <<"services.", Monito
212212
false;
213213
true ->
214214
{MatchingContent, Thread2} = case MonitorArgs of
215-
#{ ?MONITOR_EXPECTED_VALUE := ExpectedValue } ->
216-
{ok, ResolvedExpectedValue, UpdatedThread} = automate_bot_engine_variables:resolve_argument(
217-
ExpectedValue, Thread, Op),
218-
ActualValue = maps:get(?CHANNEL_MESSAGE_CONTENT, FullMessage, none),
215+
#{ ?MONITOR_EXPECTED_VALUE := ExpectedValue } ->
216+
{ok, ResolvedExpectedValue, UpdatedThread} = automate_bot_engine_variables:resolve_argument(
217+
ExpectedValue, Thread, Op),
218+
ActualValue = maps:get(?CHANNEL_MESSAGE_CONTENT, FullMessage, none),
219219
{ResolvedExpectedValue == ActualValue, UpdatedThread};
220-
_ ->
221-
{true, Thread}
222-
end,
220+
_ ->
221+
{true, Thread}
222+
end,
223223
case MatchingContent of
224224
true ->
225225
{ok, ThreadWithSavedValue} = case {MonitorArgs, FullMessage} of
@@ -231,8 +231,8 @@ trigger_thread(#program_trigger{ condition= Op=#{ ?TYPE := <<"services.", Monito
231231
{ok, Thread2}
232232
end,
233233

234-
{ok, NewThread} = automate_bot_engine_variables:set_last_monitor_value(
235-
ThreadWithSavedValue, MonitorId, FullMessage),
234+
{ok, NewThread} = automate_bot_engine_variables:set_last_bridge_value(
235+
ThreadWithSavedValue, ServiceId, FullMessage),
236236

237237
SavedThread = case {?UTILS:get_block_id(Op), FullMessage} of
238238
{undefined, _} ->
@@ -290,7 +290,7 @@ trigger_thread(Trigger, Message, ProgramState) ->
290290
%%%===================================================================
291291
%%% Aux functions
292292
%%%===================================================================
293-
trigger_thread_with_matching_message(Program, ProgramId, MonitorId, MonitorArgs, MessageContent, FullMessage) ->
293+
trigger_thread_with_matching_message(Program, ProgramId, ChannelId, MonitorArgs, MessageContent, FullMessage) ->
294294
Thread = #program_thread{ position=[1]
295295
, program=Program
296296
, global_memory=#{}
@@ -306,8 +306,14 @@ trigger_thread_with_matching_message(Program, ProgramId, MonitorId, MonitorArgs,
306306
{ok, Thread}
307307
end,
308308

309-
{ok, NewThread} = automate_bot_engine_variables:set_last_monitor_value(
310-
ThreadWithSavedValue, MonitorId, FullMessage),
309+
NewThread = case automate_service_port_engine:get_channel_origin_bridge(ChannelId) of
310+
{ok, ServiceId} ->
311+
{ok, Thread1} = automate_bot_engine_variables:set_last_bridge_value(
312+
ThreadWithSavedValue, ServiceId, FullMessage),
313+
Thread1;
314+
{error, not_found} ->
315+
Thread
316+
end,
311317
{true, NewThread}.
312318

313319
save_value(Thread, #{ ?TYPE := ?VARIABLE_VARIABLE

backend/apps/automate_bot_engine/src/automate_bot_engine_variables.erl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
, get_program_variable/2
88
, set_program_variable/3
99

10-
, set_last_monitor_value/3
11-
, get_last_monitor_value/2
10+
, set_last_bridge_value/3
11+
, get_last_bridge_value/2
1212

1313
, retrieve_thread_value/2
1414
, retrieve_thread_values/2
@@ -116,13 +116,13 @@ get_program_variable(#program_thread{ program_id=ProgramId }, Key) ->
116116
{error, not_found}
117117
end.
118118

119-
-spec set_last_monitor_value(#program_thread{}, binary(), any()) -> {ok, #program_thread{}}.
120-
set_last_monitor_value(Thread, MonitorId, Value) ->
121-
set_thread_value(Thread, [?LAST_MONITOR_VALUES, MonitorId], Value).
119+
-spec set_last_bridge_value(#program_thread{}, binary(), any()) -> {ok, #program_thread{}}.
120+
set_last_bridge_value(Thread, BridgeId, Value) ->
121+
set_thread_value(Thread, [?LAST_BRIDGE_VALUES, BridgeId], Value).
122122

123-
-spec get_last_monitor_value(#program_thread{}, binary()) -> {ok, any()} | {error, not_found}.
124-
get_last_monitor_value(Thread, MonitorId) ->
125-
get_thread_value(Thread, [?LAST_MONITOR_VALUES, MonitorId]).
123+
-spec get_last_bridge_value(#program_thread{}, binary()) -> {ok, any()} | {error, not_found}.
124+
get_last_bridge_value(Thread, BridgeId) ->
125+
get_thread_value(Thread, [?LAST_BRIDGE_VALUES, BridgeId]).
126126

127127
-spec retrieve_instruction_memory(#program_thread{}) -> {ok, any()} | {error, not_found}.
128128
retrieve_instruction_memory(#program_thread{ instruction_memory=Memory, position=Position }) ->

backend/apps/automate_bot_engine/src/instructions.hrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
-define(SERVICE_CALL_VALUES, <<"service_call_values">>).
4848
-define(SERVICE_ACTION, <<"service_action">>).
4949

50-
-define(LAST_MONITOR_VALUES, <<"__last_monitor_values__">>).
50+
-define(LAST_BRIDGE_VALUES, <<"__last_bridge_values__">>).
5151

5252
%%%% Special data
5353
-define(LIST_FILL, null).

backend/apps/automate_logging/src/automate_logging.erl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77

88
%% Application callbacks
99
-export([ log_event/2
10+
, log_signal_to_bridge_and_owner/3
11+
, get_signal_by_bridge_and_owner_history/2
1012
, log_call_to_bridge/5
1113
, log_program_error/1
1214
, log_platform/4
1315
, log_platform/2
1416
, log_api/3
1517
]).
1618

19+
-define(DEFAULT_LOG_HISTORY_RETRIEVE, 1000).
1720
-include("../../automate_storage/src/records.hrl").
1821

1922
%%====================================================================
@@ -59,6 +62,45 @@ log_event(Channel, Message) ->
5962
ok
6063
end.
6164

65+
-spec log_signal_to_bridge_and_owner(Signal :: any(), BridgeId :: binary(), Owner :: owner_id()) -> ok.
66+
log_signal_to_bridge_and_owner(Signal, BridgeId, {OwnerType, OwnerId}) ->
67+
Config = get_signal_storage_config(),
68+
case Config of
69+
#{ type := raw
70+
, url := BaseURL
71+
} ->
72+
Url = lists:flatten(io_lib:format("~s/~s_~p_~s", [BaseURL, BridgeId, OwnerType, OwnerId])),
73+
Type = "application/json",
74+
Body = jiffy:encode(Signal),
75+
Headers = [],
76+
HTTPOptions = [],
77+
Options = [],
78+
{ok, _} = httpc:request(post, {Url, Headers, Type, Body}, HTTPOptions, Options);
79+
undefined ->
80+
io:fwrite("[Error] Signal logging configuration not set")
81+
end.
82+
83+
-spec get_signal_by_bridge_and_owner_history(BridgeId :: binary(), Owner :: owner_id()) -> {ok, iolist()} | {error, _}.
84+
get_signal_by_bridge_and_owner_history(BridgeId, {OwnerType, OwnerId}) ->
85+
Config = get_signal_storage_config(),
86+
case Config of
87+
#{ type := raw
88+
, url := BaseURL
89+
} ->
90+
Url = lists:flatten(io_lib:format("~s/~s_~p_~s?q=latest&n=~p", [BaseURL, BridgeId, OwnerType, OwnerId, ?DEFAULT_LOG_HISTORY_RETRIEVE])),
91+
Headers = [],
92+
HTTPOptions = [],
93+
Options = [{body_format, binary}],
94+
{ok, { {_, StatusCode, _StatusPhrase}, _Headers, Body }
95+
} = httpc:request(get, {Url, Headers}, HTTPOptions, Options),
96+
2 = StatusCode div 100, %% Expect a 2XX status code.
97+
{ ok
98+
, [<<"[">>, binary:replace(Body, <<"\0">>, <<",">>, [global]), <<"]">>]
99+
};
100+
undefined ->
101+
{error, no_signal_logging}
102+
end.
103+
62104

63105
-spec log_call_to_bridge(binary(), binary(), binary(), binary(), map()) -> ok.
64106
log_call_to_bridge(BridgeId, FunctionName, Arguments, UserId, ExtraData) ->
@@ -140,5 +182,13 @@ get_config() ->
140182
none
141183
end.
142184

185+
get_signal_storage_config() ->
186+
case application:get_env(automate_logging, signal_storage_endpoint) of
187+
{ok, Config} ->
188+
Config;
189+
undefined ->
190+
none
191+
end.
192+
143193
get_timestamp() ->
144194
erlang:system_time(millisecond).
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
%%% @doc
2+
%%% REST endpoint to manage bridge signal history.
3+
%%% @end
4+
5+
-module(automate_rest_api_bridge_signal_history).
6+
-export([init/2]).
7+
-export([ allowed_methods/2
8+
, options/2
9+
, is_authorized/2
10+
, content_types_provided/2
11+
]).
12+
13+
-export([ to_json/2
14+
]).
15+
16+
-define(UTILS, automate_rest_api_utils).
17+
-include("./records.hrl").
18+
-include("../../automate_service_port_engine/src/records.hrl").
19+
-include("../../automate_storage/src/records.hrl").
20+
21+
-record(state, { bridge_id :: binary()
22+
, group_id :: binary() | undefined
23+
, owner :: owner_id() | undefined
24+
}).
25+
26+
-spec init(_,_) -> {'cowboy_rest',_,_}.
27+
init(Req, _Opts) ->
28+
BridgeId = cowboy_req:binding(bridge_id, Req),
29+
Qs = cowboy_req:parse_qs(Req),
30+
GroupId = proplists:get_value(<<"group_id">>, Qs),
31+
Req1 = automate_rest_api_cors:set_headers(Req),
32+
{cowboy_rest, Req1
33+
, #state{ bridge_id=BridgeId
34+
, group_id=GroupId
35+
, owner=undefined
36+
}}.
37+
38+
%% CORS
39+
options(Req, State) ->
40+
{ok, Req, State}.
41+
42+
%% Authentication
43+
-spec allowed_methods(cowboy_req:req(),_) -> {[binary()], cowboy_req:req(),_}.
44+
allowed_methods(Req, State) ->
45+
{[<<"GET">>, <<"OPTIONS">>], Req, State}.
46+
47+
is_authorized(Req, State=#state{ group_id=GroupId }) ->
48+
Req1 = automate_rest_api_cors:set_headers(Req),
49+
case cowboy_req:method(Req1) of
50+
%% Don't do authentication if it's just asking for options
51+
<<"OPTIONS">> ->
52+
{ true, Req1, State };
53+
_ ->
54+
case cowboy_req:header(<<"authorization">>, Req, undefined) of
55+
undefined ->
56+
{ {false, <<"Authorization header not found">>} , Req1, State };
57+
X ->
58+
case automate_rest_api_backend:is_valid_token_uid(X) of
59+
{true, UserId} ->
60+
case GroupId of
61+
undefined ->
62+
{ true, Req1, State#state{ owner={user, UserId} } };
63+
GId when is_binary(GId) ->
64+
case automate_storage:is_allowed_to_write_in_group({user, UserId}, GroupId) of
65+
true ->
66+
{ true, Req1, State#state{ owner={group, GroupId} } };
67+
false ->
68+
{ { false, <<"Unauthorized">>}, Req1, State }
69+
end
70+
end;
71+
false ->
72+
{ { false, <<"Authorization not correct">>}, Req1, State }
73+
end
74+
end
75+
end.
76+
77+
%% Route by Method
78+
content_types_provided(Req, State) ->
79+
{[{{<<"application">>, <<"json">>, []}, to_json}],
80+
Req, State}.
81+
82+
83+
%% GET handler
84+
to_json(Req, State=#state{bridge_id=BridgeId, owner=Owner}) ->
85+
case automate_logging:get_signal_by_bridge_and_owner_history(BridgeId, Owner) of
86+
{ok, Data} ->
87+
Req1 = ?UTILS:send_json_format(Req),
88+
%% Insert the data inside a iolist.
89+
%% This is to avoid json-encoding and decoding a potentially big JSON blob.
90+
{ [<<"{ \"success\": true, \"data\": ">>, Data, <<"}">>], Req1, State };
91+
{ error, Reason } ->
92+
Req1 = ?UTILS:send_json_output(jiffy:encode(#{ success => false, message => Reason }), Req),
93+
{ false, Req1, State }
94+
end.

0 commit comments

Comments
 (0)