Просмотр исходного кода

Merge pull request #11918 from thalesmg/add-action-types-api-r53-20231109

feat(actions_api): add `/action_types` API
Thales Macedo Garitezi 2 лет назад
Родитель
Сommit
8df6ce812b

+ 28 - 2
apps/emqx_bridge/src/emqx_bridge_v2_api.erl

@@ -40,7 +40,8 @@
     '/actions/:id/enable/:enable'/2,
     '/actions/:id/:operation'/2,
     '/nodes/:node/actions/:id/:operation'/2,
-    '/actions_probe'/2
+    '/actions_probe'/2,
+    '/action_types'/2
 ]).
 
 %% BpAPI
@@ -79,7 +80,8 @@ paths() ->
         "/actions/:id/enable/:enable",
         "/actions/:id/:operation",
         "/nodes/:node/actions/:id/:operation",
-        "/actions_probe"
+        "/actions_probe",
+        "/action_types"
     ].
 
 error_schema(Code, Message) when is_atom(Code) ->
@@ -338,6 +340,27 @@ schema("/actions_probe") ->
                 400 => error_schema(['TEST_FAILED'], "bridge test failed")
             }
         }
+    };
+schema("/action_types") ->
+    #{
+        'operationId' => '/action_types',
+        get => #{
+            tags => [<<"actions">>],
+            desc => ?DESC("desc_api10"),
+            summary => <<"List available action types">>,
+            responses => #{
+                200 => emqx_dashboard_swagger:schema_with_examples(
+                    array(emqx_bridge_v2_schema:types_sc()),
+                    #{
+                        <<"types">> =>
+                            #{
+                                summary => <<"Action types">>,
+                                value => emqx_bridge_v2_schema:types()
+                            }
+                    }
+                )
+            }
+        }
     }.
 
 '/actions'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
@@ -486,6 +509,9 @@ schema("/actions_probe") ->
             redact(BadRequest)
     end.
 
+'/action_types'(get, _Request) ->
+    ?OK(emqx_bridge_v2_schema:types()).
+
 maybe_deobfuscate_bridge_probe(#{<<"type">> := BridgeType, <<"name">> := BridgeName} = Params) ->
     case emqx_bridge:lookup(BridgeType, BridgeName) of
         {ok, #{raw_config := RawConf}} ->

+ 19 - 0
apps/emqx_bridge/src/schema/emqx_bridge_v2_schema.erl

@@ -30,9 +30,18 @@
     post_request/0
 ]).
 
+-export([types/0, types_sc/0]).
+
 -export([enterprise_api_schemas/1]).
 
+-export_type([action_type/0]).
+
+%% Should we explicitly list them here so dialyzer may be more helpful?
+-type action_type() :: atom().
+
 -if(?EMQX_RELEASE_EDITION == ee).
+-spec enterprise_api_schemas(Method) -> [{_Type :: binary(), ?R_REF(module(), Method)}] when
+    Method :: string().
 enterprise_api_schemas(Method) ->
     %% We *must* do this to ensure the module is really loaded, especially when we use
     %% `call_hocon' from `nodetool' to generate initial configurations.
@@ -55,6 +64,8 @@ enterprise_fields_actions() ->
 
 -else.
 
+-spec enterprise_api_schemas(Method) -> [{_Type :: binary(), ?R_REF(module(), Method)}] when
+    Method :: string().
 enterprise_api_schemas(_Method) -> [].
 
 enterprise_fields_actions() -> [].
@@ -129,6 +140,14 @@ desc(actions) ->
 desc(_) ->
     undefined.
 
+-spec types() -> [action_type()].
+types() ->
+    proplists:get_keys(?MODULE:fields(actions)).
+
+-spec types_sc() -> ?ENUM([action_type()]).
+types_sc() ->
+    hoconsc:enum(types()).
+
 -ifdef(TEST).
 -include_lib("hocon/include/hocon_types.hrl").
 schema_homogeneous_test() ->

+ 25 - 3
apps/emqx_bridge/test/emqx_bridge_v2_api_SUITE.erl

@@ -236,6 +236,14 @@ end_per_group(_, Config) ->
     emqx_cth_suite:stop(?config(group_apps, Config)),
     ok.
 
+init_per_testcase(t_action_types, Config) ->
+    case ?config(cluster_nodes, Config) of
+        undefined ->
+            init_mocks();
+        Nodes ->
+            [erpc:call(Node, ?MODULE, init_mocks, []) || Node <- Nodes]
+    end,
+    Config;
 init_per_testcase(_TestCase, Config) ->
     case ?config(cluster_nodes, Config) of
         undefined ->
@@ -260,8 +268,14 @@ end_per_testcase(_TestCase, Config) ->
 
 -define(CONNECTOR_IMPL, emqx_bridge_v2_dummy_connector).
 init_mocks() ->
-    meck:new(emqx_connector_ee_schema, [passthrough, no_link]),
-    meck:expect(emqx_connector_ee_schema, resource_type, 1, ?CONNECTOR_IMPL),
+    case emqx_release:edition() of
+        ee ->
+            meck:new(emqx_connector_ee_schema, [passthrough, no_link]),
+            meck:expect(emqx_connector_ee_schema, resource_type, 1, ?CONNECTOR_IMPL),
+            ok;
+        ce ->
+            ok
+    end,
     meck:new(?CONNECTOR_IMPL, [non_strict, no_link]),
     meck:expect(?CONNECTOR_IMPL, callback_mode, 0, async_if_possible),
     meck:expect(
@@ -289,7 +303,7 @@ init_mocks() ->
     ok = meck:expect(?CONNECTOR_IMPL, on_get_channels, fun(ResId) ->
         emqx_bridge_v2:get_channels_for_connector(ResId)
     end),
-    [?CONNECTOR_IMPL, emqx_connector_ee_schema].
+    ok.
 
 clear_resources() ->
     lists:foreach(
@@ -886,6 +900,14 @@ t_cascade_delete_actions(Config) ->
     ),
     {ok, 200, []} = request_json(get, uri([?ROOT]), Config).
 
+t_action_types(Config) ->
+    Res = request_json(get, uri(["action_types"]), Config),
+    ?assertMatch({ok, 200, _}, Res),
+    {ok, 200, Types} = Res,
+    ?assert(is_list(Types), #{types => Types}),
+    ?assert(lists:all(fun is_binary/1, Types), #{types => Types}),
+    ok.
+
 %%% helpers
 listen_on_random_port() ->
     SockOpts = [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {backlog, 1000}],

+ 6 - 0
rel/i18n/emqx_bridge_v2_api.hocon

@@ -54,6 +54,12 @@ desc_api9.desc:
 desc_api9.label:
 """Test Bridge Creation"""
 
+desc_api10.desc:
+"""Lists the available action types."""
+
+desc_api10.label:
+"""List action types"""
+
 desc_bridge_metrics.desc:
 """Get bridge metrics by id."""