From eabd09051aa77714c53ed912d459d94003e1191d Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 9 Nov 2023 13:54:57 -0300 Subject: [PATCH] feat(actions_api): add `/action_types` API --- apps/emqx_bridge/src/emqx_bridge_v2_api.erl | 30 +++++++++++++++++-- .../src/schema/emqx_bridge_v2_schema.erl | 19 ++++++++++++ .../test/emqx_bridge_v2_api_SUITE.erl | 28 +++++++++++++++-- rel/i18n/emqx_bridge_v2_api.hocon | 6 ++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/apps/emqx_bridge/src/emqx_bridge_v2_api.erl b/apps/emqx_bridge/src/emqx_bridge_v2_api.erl index a8d634963..1935a1b5a 100644 --- a/apps/emqx_bridge/src/emqx_bridge_v2_api.erl +++ b/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}} -> diff --git a/apps/emqx_bridge/src/schema/emqx_bridge_v2_schema.erl b/apps/emqx_bridge/src/schema/emqx_bridge_v2_schema.erl index d6d8eb9a1..1d059903a 100644 --- a/apps/emqx_bridge/src/schema/emqx_bridge_v2_schema.erl +++ b/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() -> diff --git a/apps/emqx_bridge/test/emqx_bridge_v2_api_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_v2_api_SUITE.erl index bf2ac51a2..9879fe1e6 100644 --- a/apps/emqx_bridge/test/emqx_bridge_v2_api_SUITE.erl +++ b/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}], diff --git a/rel/i18n/emqx_bridge_v2_api.hocon b/rel/i18n/emqx_bridge_v2_api.hocon index 1f2c2bd8d..23a75712a 100644 --- a/rel/i18n/emqx_bridge_v2_api.hocon +++ b/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."""