From 25ccda7faa160486d18e3711bfaf6eae87361c2d Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Mon, 5 Aug 2024 18:35:17 +0100 Subject: [PATCH 01/14] Add initial rapidpro connector setup --- .../api_connector/abstract_connector.rb | 2 +- .../api_connector/rapidpro_connector.rb | 31 +++++++++++++++++++ .../api_connector/rapidpro_connector_spec.rb | 18 +++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 app/services/api_connector/rapidpro_connector.rb create mode 100644 spec/services/api_connector/rapidpro_connector_spec.rb diff --git a/app/services/api_connector/abstract_connector.rb b/app/services/api_connector/abstract_connector.rb index e0f9009abc..08dba93a75 100644 --- a/app/services/api_connector/abstract_connector.rb +++ b/app/services/api_connector/abstract_connector.rb @@ -24,7 +24,7 @@ def id self.class.id end - def default_env_prefix + def self.default_env_prefix "PRIMERO_CONNECT_API_#{id.upcase}_" end diff --git a/app/services/api_connector/rapidpro_connector.rb b/app/services/api_connector/rapidpro_connector.rb new file mode 100644 index 0000000000..ac1ab2dec2 --- /dev/null +++ b/app/services/api_connector/rapidpro_connector.rb @@ -0,0 +1,31 @@ +class ApiConnector::RapidproConnector < ApiConnector::AbstractConnector + IDENTIFIER = 'rp' + + def self.id + IDENTIFIER + end + + def self.build_from_env(options = {}) + prefix = options[:prefix] || default_env_prefix + config = ENV.select { |key, _| key.start_with?(prefix) } + .transform_keys { |key| key.delete_prefix(prefix).downcase } + .with_indifferent_access + + token = config.delete(:token) + config['default_headers'] = { + Authorization: "Token #{token}" + } + new(config) + end + + def send_message(urn, text) + connection.post('/api/v2/broadcasts.json', post_params(urn, text)) + end + + def post_params(urn, text) + { + urns: [urn], + text: + } + end +end diff --git a/spec/services/api_connector/rapidpro_connector_spec.rb b/spec/services/api_connector/rapidpro_connector_spec.rb new file mode 100644 index 0000000000..9c48f170c1 --- /dev/null +++ b/spec/services/api_connector/rapidpro_connector_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +describe ApiConnector::RapidproConnector do + let(:connection) { double('connection') } + let(:connector) do + c = ApiConnector::RapidproConnector.new + (c.connection = connection) && c + end + + describe '.send_message' do + it 'calls the rapidpro broadcasts endpoint with the desired output' do + expect(connection).to( + receive(:post).with('/api/v2/broadcasts.json', { text: 'test message', urns: ['tel:+11234561234'] }) + ) + connector.send_message('tel:+11234561234', 'test message') + end + end +end From 9ad66acf7e95bcf1fecce356dd822fcd57658e35 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 26 Sep 2024 13:21:45 +0100 Subject: [PATCH 02/14] Add initial plumbing for new messages record type --- .../components/pages/admin/roles-form/constants.js | 3 ++- app/javascript/components/permissions/constants.js | 8 +++++--- app/javascript/components/permissions/index.js | 3 ++- app/javascript/config.js | 11 +++++++++-- app/models/message.rb | 2 ++ app/models/permission.rb | 6 ++++-- db/migrate/20240926130958_create_message.rb | 8 ++++++++ spec/models/message_spec.rb | 5 +++++ 8 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 app/models/message.rb create mode 100644 db/migrate/20240926130958_create_message.rb create mode 100644 spec/models/message_spec.rb diff --git a/app/javascript/components/pages/admin/roles-form/constants.js b/app/javascript/components/pages/admin/roles-form/constants.js index 44243b4483..c888213809 100644 --- a/app/javascript/components/pages/admin/roles-form/constants.js +++ b/app/javascript/components/pages/admin/roles-form/constants.js @@ -38,7 +38,8 @@ export const RESOURCES = [ "activity_log", "matching_configuration", "duplicate", - "kpi" + "kpi", + "message", ]; export const ROLES_PERMISSIONS = Object.freeze({ diff --git a/app/javascript/components/permissions/constants.js b/app/javascript/components/permissions/constants.js index e2160bbc3a..7f6695efd6 100644 --- a/app/javascript/components/permissions/constants.js +++ b/app/javascript/components/permissions/constants.js @@ -120,7 +120,7 @@ export const ACTIONS = { WRITE: "write", VIEW_FAMILY_RECORD: "view_family_record", LINK_FAMILY_RECORD: "link_family_record", - REMOVE_ALERT: "remove_alert" + REMOVE_ALERT: "remove_alert", }; export const MANAGE = [ACTIONS.MANAGE]; @@ -151,7 +151,8 @@ export const RESOURCES = { tracing_requests: "tracing_requests", user_groups: "user_groups", users: "users", - webhooks: "webhooks" + webhooks: "webhooks", + messages: "messages", }; export const RECORD_RESOURCES = [RESOURCES.cases, RESOURCES.incidents, RESOURCES.tracing_requests]; @@ -166,7 +167,8 @@ export const ADMIN_RESOURCES = [ RESOURCES.forms, RESOURCES.metadata, RESOURCES.audit_logs, - RESOURCES.webhooks + RESOURCES.webhooks, + RESOURCES.messages ]; export const SEARCH_OTHERS = [...MANAGE, ACTIONS.SEARCH_OWNED_BY_OTHERS]; diff --git a/app/javascript/components/permissions/index.js b/app/javascript/components/permissions/index.js index 2b06ff994b..5e07df8813 100644 --- a/app/javascript/components/permissions/index.js +++ b/app/javascript/components/permissions/index.js @@ -52,7 +52,8 @@ export { VIEW_KPIS, WRITE_RECORDS, WRITE_REGISTRY_RECORD, - REMOVE_ALERT + REMOVE_ALERT, + } from "./constants"; export { checkPermissions } from "./utils"; diff --git a/app/javascript/config.js b/app/javascript/config.js index 8cec2a2f22..45d4583cd0 100644 --- a/app/javascript/config.js +++ b/app/javascript/config.js @@ -19,7 +19,7 @@ import { SHOW_SUMMARY, READ_MANAGED_REPORTS, READ_REGISTRY_RECORD, - READ_FAMILY_RECORD + READ_FAMILY_RECORD, } from "./components/permissions/constants"; import getAdminResources from "./components/pages/admin/utils/get-admin-resources"; @@ -137,7 +137,8 @@ const RECORD_PATH = { users: "users", activity_log: "activity_log", registry_records: "registry_records", - webpush_config: "webpush/config" + webpush_config: "webpush/config", + messages: "messages" }; const RECORD_INFORMATION_GROUP = "record_information"; @@ -353,6 +354,12 @@ const ADMIN_NAV = [ label: "settings.navigation.audit_logs", permission: SHOW_AUDIT_LOGS, recordType: RESOURCES.audit_logs + }, + { + to: "/messages", + label: "settings.navigation.messages", + permission: MANAGE, + recordType: RESOURCES.messages } ]; diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000000..28bdd9d601 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,2 @@ +class Message < ApplicationRecord +end diff --git a/app/models/permission.rb b/app/models/permission.rb index 1822224f42..551dc39ce6 100644 --- a/app/models/permission.rb +++ b/app/models/permission.rb @@ -183,6 +183,7 @@ class Permission < ValueObject LINK_INCIDENT_TO_CASE = 'link_incident_to_case' LINK_FAMILY_RECORD = 'link_family_record' REMOVE_ALERT = 'remove_alert' + MESSAGE = 'message' RESOURCE_ACTIONS = { CASE => [ @@ -257,7 +258,8 @@ class Permission < ValueObject KPI_SUPERVISOR_TO_CASEWORKER_RATIO, KPI_TIME_FROM_CASE_OPEN_TO_CLOSE ], CODE_OF_CONDUCT => [MANAGE], - ACTIVITY_LOG => [MANAGE, TRANSFER] + ACTIVITY_LOG => [MANAGE, TRANSFER], + MESSAGE => [MANAGE] }.freeze RESOURCE_FORM_ACTIONS = { @@ -317,7 +319,7 @@ def actions def resources [CASE, INCIDENT, TRACING_REQUEST, POTENTIAL_MATCH, REGISTRY_RECORD, ROLE, USER, USER_GROUP, AGENCY, WEBHOOK, METADATA, SYSTEM, REPORT, MANAGED_REPORT, DASHBOARD, AUDIT_LOG, MATCHING_CONFIGURATION, DUPLICATE, - CODE_OF_CONDUCT, ACTIVITY_LOG] + CODE_OF_CONDUCT, ACTIVITY_LOG, MESSAGE] end def records diff --git a/db/migrate/20240926130958_create_message.rb b/db/migrate/20240926130958_create_message.rb new file mode 100644 index 0000000000..2f010f425a --- /dev/null +++ b/db/migrate/20240926130958_create_message.rb @@ -0,0 +1,8 @@ +class CreateMessage < ActiveRecord::Migration[6.1] + def change + create_table :messages do |t| + t.text :body + t.timestamps + end + end +end diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb new file mode 100644 index 0000000000..d74b6a8e20 --- /dev/null +++ b/spec/models/message_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Message, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 6942e67501d646c3b5e95926a36c3f3c7394440f Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 26 Sep 2024 14:44:16 +0100 Subject: [PATCH 03/14] Add rapidpro fields to user --- .../components/pages/admin/users-form/constants.js | 4 +++- .../components/pages/admin/users-form/form.js | 5 +++++ app/models/user.rb | 4 ++++ db/schema.rb | 10 +++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/pages/admin/users-form/constants.js b/app/javascript/components/pages/admin/users-form/constants.js index b29d00241a..76df389b88 100644 --- a/app/javascript/components/pages/admin/users-form/constants.js +++ b/app/javascript/components/pages/admin/users-form/constants.js @@ -78,7 +78,9 @@ export const FIELD_NAMES = Object.freeze({ RECEIVE_WEBPUSH_APPROVAL_REQUEST: "settings.notifications.receive_webpush.approval_request", RECEIVE_WEBPUSH_APPROVAL_RESPONSE: "settings.notifications.receive_webpush.approval_response", RECEIVE_WEBPUSH_TRANSITION_NOTIFICATION: "settings.notifications.receive_webpush.transition_notification", - RECEIVE_WEBPUSH_TRANSFER_REQUEST: "settings.notifications.receive_webpush.transfer_request" + RECEIVE_WEBPUSH_TRANSFER_REQUEST: "settings.notifications.receive_webpush.transfer_request", + RAPIDPRO_URN: "rapidpro_urn", + RECEIVE_MESSAGES: "receive_messages" }); export const NOTIFICATIONS_PREFERENCES = [ diff --git a/app/javascript/components/pages/admin/users-form/form.js b/app/javascript/components/pages/admin/users-form/form.js index 308b6a5d40..1c74eca262 100644 --- a/app/javascript/components/pages/admin/users-form/form.js +++ b/app/javascript/components/pages/admin/users-form/form.js @@ -204,6 +204,11 @@ const sharedUserFields = ( option_strings_source: OPTION_TYPES.LOCATION, required: true }, + { + display_name: i18n.t("user.rapidpro_urn"), + name: FIELD_NAMES.RAPIDPRO_URN, + type: TEXT_FIELD, + }, { display_name: i18n.t("user.disabled"), name: FIELD_NAMES.DISABLED, diff --git a/app/models/user.rb b/app/models/user.rb index b058b6d27f..04926f37ea 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -531,6 +531,10 @@ def receive_webpush? receive_webpush == true && !disabled? end + def messageable? + receive_messages == true && !disabled? + end + def authorized_referral_roles(record) return Role.none unless record.respond_to?(:referrals_to_user) diff --git a/db/schema.rb b/db/schema.rb index c6c1af565c..4fa5a67ef0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_03_06_154915) do +ActiveRecord::Schema.define(version: 2024_09_26_133159) do # These are extensions that must be enabled in order to support this database enable_extension "ltree" @@ -387,6 +387,12 @@ t.index ["unique_id"], name: "index_lookups_on_unique_id", unique: true end + create_table "messages", force: :cascade do |t| + t.text "body" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "perpetrators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.jsonb "data", default: {} t.index ["data"], name: "index_perpetrators_on_data", using: :gin @@ -668,6 +674,8 @@ t.bigint "code_of_conduct_id" t.boolean "receive_webpush" t.jsonb "settings" + t.text "rapidpro_urn" + t.boolean "receive_messages" t.index ["agency_id"], name: "index_users_on_agency_id" t.index ["code_of_conduct_id"], name: "index_users_on_code_of_conduct_id" t.index ["email"], name: "index_users_on_email", unique: true From 5fa88c97486883d5c9df5b2b78486988b29bb67c Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Tue, 22 Oct 2024 13:13:23 +0100 Subject: [PATCH 04/14] WIP on messages-list component --- .../components/pages/admin/index.js | 1 + .../pages/admin/messages-list/actions.js | 10 +++++++ .../pages/admin/messages-list/component.jsx | 27 +++++++++++++++++++ .../pages/admin/messages-list/index.js | 3 +++ .../pages/admin/messages-list/namespace.js | 1 + .../pages/admin/messages-list/reducer.js | 0 app/javascript/components/pages/index.js | 1 + app/javascript/config.js | 1 + app/javascript/routes.js | 8 +++++- config/locales/en.yml | 1 + 10 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 app/javascript/components/pages/admin/messages-list/actions.js create mode 100644 app/javascript/components/pages/admin/messages-list/component.jsx create mode 100644 app/javascript/components/pages/admin/messages-list/index.js create mode 100644 app/javascript/components/pages/admin/messages-list/namespace.js create mode 100644 app/javascript/components/pages/admin/messages-list/reducer.js diff --git a/app/javascript/components/pages/admin/index.js b/app/javascript/components/pages/admin/index.js index 4fb615e75f..9df400202f 100644 --- a/app/javascript/components/pages/admin/index.js +++ b/app/javascript/components/pages/admin/index.js @@ -18,4 +18,5 @@ export { default as AuditLogs } from "./audit-logs"; export { default as ConfigurationsList } from "./configurations-list"; export { default as ConfigurationsForm } from "./configurations-form"; export { default as LocationsList } from "./locations-list"; +export { default as MessagesList } from "./messages-list"; export { default as CodeOfConduct } from "./code-of-conduct"; diff --git a/app/javascript/components/pages/admin/messages-list/actions.js b/app/javascript/components/pages/admin/messages-list/actions.js new file mode 100644 index 0000000000..7655707f45 --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/actions.js @@ -0,0 +1,10 @@ +import { namespaceActions } from "../../../../libs"; +import namespace from "./namespace"; + +namespaceActions(namespace, [ + "FETCH_MESSAGES", + "FETCH_MESSAGES_STARTED", + "FETCH_MESSAGES_SUCCESS", + "FETCH_MESSAGES_FAILURE", + "FETCH_MESSAGES_FINISHED", +]); \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx new file mode 100644 index 0000000000..04f04eeb3d --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -0,0 +1,27 @@ +import PageContent from "../../../page/components/page-content"; +import PageHeading from "../../../page/components/page-heading"; +import Permission, { MANAGE, RESOURCES } from "../../../permissions"; +import IndexTable from "../../../index-table"; +import { useI18n } from "../../../i18n"; + +function Component() { + const i18n = useI18n(); + const tableOptions = { + onTableCHange: () => {}, + } + return ( + + + +

Hello world

+ {/* */} +
+
+ ) +} + +Component.propTypes = {} +Component.displayName = "MessagesList"; + +export default Component; + diff --git a/app/javascript/components/pages/admin/messages-list/index.js b/app/javascript/components/pages/admin/messages-list/index.js new file mode 100644 index 0000000000..aa06c80c94 --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/index.js @@ -0,0 +1,3 @@ +export { default } from "./component"; +export { default as NAMESPACE } from "./namespace"; +export { default as reducer } from "./reducer"; diff --git a/app/javascript/components/pages/admin/messages-list/namespace.js b/app/javascript/components/pages/admin/messages-list/namespace.js new file mode 100644 index 0000000000..aab483edcd --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/namespace.js @@ -0,0 +1 @@ +export default "messages"; \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/reducer.js b/app/javascript/components/pages/admin/messages-list/reducer.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/javascript/components/pages/index.js b/app/javascript/components/pages/index.js index 327c962d29..a8a7b7af75 100644 --- a/app/javascript/components/pages/index.js +++ b/app/javascript/components/pages/index.js @@ -25,5 +25,6 @@ export { ConfigurationsList, ConfigurationsForm, LocationsList, + MessagesList, CodeOfConduct } from "./admin"; diff --git a/app/javascript/config.js b/app/javascript/config.js index 45d4583cd0..5ea65c95f9 100644 --- a/app/javascript/config.js +++ b/app/javascript/config.js @@ -182,6 +182,7 @@ const ROUTES = { admin_users: "/admin/users", admin_users_new: "/admin/users/new", audit_logs: "/admin/audit_logs", + admin_messages: "/admin/messages", cases: "/cases", configurations: "/admin/configurations", admin_configurations_new: "/admin/configurations/new", diff --git a/app/javascript/routes.js b/app/javascript/routes.js index 6d0c94bfea..8d9ba6d26b 100644 --- a/app/javascript/routes.js +++ b/app/javascript/routes.js @@ -25,6 +25,7 @@ import { UsersList, ConfigurationsList, ConfigurationsForm, + MessagesList, CodeOfConduct as AdminCodeOfConduct, Support } from "./components/pages"; @@ -449,6 +450,11 @@ export default [ component: RolesList, resources: RESOURCES.roles }, + { + path: ROUTES.admin_messages, + component: MessagesList, + resources: RESOURCES.messages + }, { path: `${ROUTES.forms}/new`, component: FormBuilder, @@ -495,7 +501,7 @@ export default [ path: ROUTES.locations, component: LocationsList, resources: RESOURCES.locations - } + }, ] } }, diff --git a/config/locales/en.yml b/config/locales/en.yml index e96122072b..e71944d0ef 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4187,6 +4187,7 @@ en: users: Users roles: Roles code_of_conduct: Code of Conduct + messages: Messages title: Settings system_settings: label: System Settings From 5e577a6190727a950b04ef5d64973e386a6078d2 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Tue, 22 Oct 2024 20:54:25 +0100 Subject: [PATCH 05/14] WIP add actions and reducers etc as well as controller and views in api --- app/controllers/api/v2/messages_controller.rb | 9 +++++++ .../admin/messages-list/action-creators.js | 14 +++++++++++ .../pages/admin/messages-list/actions.js | 2 +- .../pages/admin/messages-list/component.jsx | 25 +++++++++++-------- .../pages/admin/messages-list/reducer.js | 19 ++++++++++++++ app/javascript/reducer.js | 4 ++- app/services/primero_model_service.rb | 2 +- .../api/v2/messages/_message.json.jbuilder | 1 + app/views/api/v2/messages/index.json.jbuilder | 3 +++ app/views/api/v2/messages/show.json.jbuilder | 1 + config/routes.rb | 2 ++ ...240926133159_add_message_fields_to_user.rb | 6 +++++ 12 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 app/controllers/api/v2/messages_controller.rb create mode 100644 app/javascript/components/pages/admin/messages-list/action-creators.js create mode 100644 app/views/api/v2/messages/_message.json.jbuilder create mode 100644 app/views/api/v2/messages/index.json.jbuilder create mode 100644 app/views/api/v2/messages/show.json.jbuilder create mode 100644 db/migrate/20240926133159_add_message_fields_to_user.rb diff --git a/app/controllers/api/v2/messages_controller.rb b/app/controllers/api/v2/messages_controller.rb new file mode 100644 index 0000000000..f67bd4de67 --- /dev/null +++ b/app/controllers/api/v2/messages_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Api::V2::MessagesController < ApplicationApiController + include Api::V2::Concerns::Pagination + include Api::V2::Concerns::JsonValidateParams + def index + @messages = Message.all + end +end diff --git a/app/javascript/components/pages/admin/messages-list/action-creators.js b/app/javascript/components/pages/admin/messages-list/action-creators.js new file mode 100644 index 0000000000..de75858895 --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/action-creators.js @@ -0,0 +1,14 @@ + +import { RECORD_PATH } from "../../../../config"; +import actions from "./actions"; +export const fetchMessages = params => { + const { data } = params || {}; + + return { + type: actions.FETCH_MESSAGES, + api: { + path: RECORD_PATH.messages, + params: data + } + } +} \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/actions.js b/app/javascript/components/pages/admin/messages-list/actions.js index 7655707f45..108cd3c89b 100644 --- a/app/javascript/components/pages/admin/messages-list/actions.js +++ b/app/javascript/components/pages/admin/messages-list/actions.js @@ -1,7 +1,7 @@ import { namespaceActions } from "../../../../libs"; import namespace from "./namespace"; -namespaceActions(namespace, [ +export default namespaceActions(namespace, [ "FETCH_MESSAGES", "FETCH_MESSAGES_STARTED", "FETCH_MESSAGES_SUCCESS", diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx index 04f04eeb3d..e971599a08 100644 --- a/app/javascript/components/pages/admin/messages-list/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -3,25 +3,30 @@ import PageHeading from "../../../page/components/page-heading"; import Permission, { MANAGE, RESOURCES } from "../../../permissions"; import IndexTable from "../../../index-table"; import { useI18n } from "../../../i18n"; +import {default as NAMESPACE} from "./namespace"; +import { fetchMessages } from "./action-creators"; function Component() { - const i18n = useI18n(); - const tableOptions = { - onTableCHange: () => {}, - } - return ( + const i18n = useI18n(); + const tableOptions = { + recordType: ["admin", NAMESPACE], + columns: [ + { name: "created_at", label: i18n.t("messages.attributes.created_at") }, + { name: "body", label: i18n.t("messages.attributes.body") } + ], + onTableChange: fetchMessages, + }; + return ( -

Hello world

- {/* */} +
- ) + ); } -Component.propTypes = {} +Component.propTypes = {}; Component.displayName = "MessagesList"; export default Component; - diff --git a/app/javascript/components/pages/admin/messages-list/reducer.js b/app/javascript/components/pages/admin/messages-list/reducer.js index e69de29bb2..648b94fadf 100644 --- a/app/javascript/components/pages/admin/messages-list/reducer.js +++ b/app/javascript/components/pages/admin/messages-list/reducer.js @@ -0,0 +1,19 @@ +import { fromJS } from "immutable"; +import actions from "./actions"; + +const DEFAULT_STATE = fromJS({}); + +export default (state = DEFAULT_STATE, {type, payload}) => { + switch (type) { + case actions.FETCH_MESSAGES_STARTED: + return state.set("loading", fromJS(payload)).set("errors", false); + case actions.FETCH_MESSAGES_SUCCESS: + return state.set("data", fromJS(payload.data)) + case actions.FETCH_MESSAGES_FAILURE: + return state.set(errors, true).set("loading", false); + case actions.FETCH_MESSAGES_FINISHED: + return state.set("errors", false).set("loading", false); + default: + return state; + } +} \ No newline at end of file diff --git a/app/javascript/reducer.js b/app/javascript/reducer.js index 21a6b4a5b5..85ddab49ac 100644 --- a/app/javascript/reducer.js +++ b/app/javascript/reducer.js @@ -52,6 +52,7 @@ import { reducer as kpiReducer } from "./components/key-performance-indicators"; import { reducer as configurationsListReducer } from "./components/pages/admin/configurations-list"; import { reducer as configurationsFormReducer } from "./components/pages/admin/configurations-form"; import { reducer as activityLogReducer } from "./components/activity-log"; +import { reducer as messageListReducer} from "./components/pages/admin/messages-list"; import { reducer as locationsListReducer, importReducer as locationsImportReducer @@ -115,7 +116,8 @@ const rootReducer = { roles: reduceReducers(initialState, rolesListReducer, rolesFormReducer), lookups: reduceReducers(initialState, lookupsListReducer, AdminLookupsFormReducers), configurations: reduceReducers(initialState, configurationsListReducer, configurationsFormReducer), - locations: reduceReducers(initialState, locationsListReducer, locationsImportReducer) + locations: reduceReducers(initialState, locationsListReducer, locationsImportReducer), + messages: reduceReducers(initialState, messageListReducer) }), activity_logs: activityLogReducer, ...kpiReducer diff --git a/app/services/primero_model_service.rb b/app/services/primero_model_service.rb index db9e299b11..59e0eeb496 100644 --- a/app/services/primero_model_service.rb +++ b/app/services/primero_model_service.rb @@ -10,7 +10,7 @@ class PrimeroModelService ReportableFollowUp ReportableProtectionConcern ReportableService Dashboard Flag Alert Attachment AuditLog BulkExport RecordHistory SavedSearch Transition Task ActivityLog Agency ContactInformation Field FormSection Location Lookup PrimeroModule PrimeroProgram Report User Role - Permission SystemSettings UserGroup ExportConfiguration PrimeroConfiguration Webhook IdentityProvider Kpi + Permission SystemSettings UserGroup ExportConfiguration PrimeroConfiguration Webhook IdentityProvider Kpi Message ].freeze def self.to_model(name) diff --git a/app/views/api/v2/messages/_message.json.jbuilder b/app/views/api/v2/messages/_message.json.jbuilder new file mode 100644 index 0000000000..0be7f95314 --- /dev/null +++ b/app/views/api/v2/messages/_message.json.jbuilder @@ -0,0 +1 @@ +json.extract! message, :id, :created_at, :updated_at, :body diff --git a/app/views/api/v2/messages/index.json.jbuilder b/app/views/api/v2/messages/index.json.jbuilder new file mode 100644 index 0000000000..9b2b8040f7 --- /dev/null +++ b/app/views/api/v2/messages/index.json.jbuilder @@ -0,0 +1,3 @@ +json.data do + json.array! @messages, partial: 'api/v2/messages/message', as: :message +end diff --git a/app/views/api/v2/messages/show.json.jbuilder b/app/views/api/v2/messages/show.json.jbuilder new file mode 100644 index 0000000000..a500e40f00 --- /dev/null +++ b/app/views/api/v2/messages/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "messages/message", message: @message diff --git a/config/routes.rb b/config/routes.rb index 1b73c4e2cb..25fb03887e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ # Copyright (c) 2014 - 2023 UNICEF. All rights reserved. Rails.application.routes.draw do + get 'message/index' root to: 'home#v2' scope :v2 do @@ -137,6 +138,7 @@ resources :key_performance_indicators, path: :kpis, only: [:show] resources :codes_of_conduct, only: %i[index create], controller: 'codes_of_conduct' resources :activity_log, only: [:index] + resources :messages, only: [:index] resources :managed_reports, only: %i[index show] do collection do get :export, to: 'managed_reports#export' diff --git a/db/migrate/20240926133159_add_message_fields_to_user.rb b/db/migrate/20240926133159_add_message_fields_to_user.rb new file mode 100644 index 0000000000..bd0b352598 --- /dev/null +++ b/db/migrate/20240926133159_add_message_fields_to_user.rb @@ -0,0 +1,6 @@ +class AddMessageFieldsToUser < ActiveRecord::Migration[6.1] + def change + add_column :users, :rapidpro_urn, :text + add_column :users, :receive_messages, :boolean + end +end From 67399347c434afc3643994e0f9e4c10652c5c4f4 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Wed, 23 Oct 2024 17:42:04 +0100 Subject: [PATCH 06/14] Add scaffolding for message-dialog modal --- .../admin/messages-list/action-creators.js | 29 +++++++++++++------ .../pages/admin/messages-list/actions.js | 3 ++ .../pages/admin/messages-list/component.jsx | 23 +++++++++++++-- .../message-dialog/component.jsx | 13 +++++++++ .../messages-list/message-dialog/index.js | 1 + 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx create mode 100644 app/javascript/components/pages/admin/messages-list/message-dialog/index.js diff --git a/app/javascript/components/pages/admin/messages-list/action-creators.js b/app/javascript/components/pages/admin/messages-list/action-creators.js index de75858895..969e4a72a9 100644 --- a/app/javascript/components/pages/admin/messages-list/action-creators.js +++ b/app/javascript/components/pages/admin/messages-list/action-creators.js @@ -1,14 +1,25 @@ - import { RECORD_PATH } from "../../../../config"; import actions from "./actions"; export const fetchMessages = params => { - const { data } = params || {}; + const { data } = params || {}; + + return { + type: actions.FETCH_MESSAGES, + api: { + path: RECORD_PATH.messages, + params: data + } + }; +}; - return { - type: actions.FETCH_MESSAGES, - api: { - path: RECORD_PATH.messages, - params: data - } +// TODO make this handle updates (if we want that as functionality) +export const saveMessage = ({message}) => { + return { + type: actions.SAVE_MESSAGE, + api: { + path: RECORD_PATH.messages, + method: "POST", + body: message } -} \ No newline at end of file + }; +}; diff --git a/app/javascript/components/pages/admin/messages-list/actions.js b/app/javascript/components/pages/admin/messages-list/actions.js index 108cd3c89b..7a784efa82 100644 --- a/app/javascript/components/pages/admin/messages-list/actions.js +++ b/app/javascript/components/pages/admin/messages-list/actions.js @@ -7,4 +7,7 @@ export default namespaceActions(namespace, [ "FETCH_MESSAGES_SUCCESS", "FETCH_MESSAGES_FAILURE", "FETCH_MESSAGES_FINISHED", + "SAVE_MESSAGE", + "SAVE_MESSAGE_SUCCESS", + "SAVE_MESSAGE_FAILURE", ]); \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx index e971599a08..3171640cd4 100644 --- a/app/javascript/components/pages/admin/messages-list/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -3,22 +3,39 @@ import PageHeading from "../../../page/components/page-heading"; import Permission, { MANAGE, RESOURCES } from "../../../permissions"; import IndexTable from "../../../index-table"; import { useI18n } from "../../../i18n"; -import {default as NAMESPACE} from "./namespace"; +import { default as NAMESPACE } from "./namespace"; import { fetchMessages } from "./action-creators"; +import ActionButton, { ACTION_BUTTON_TYPES } from "../../../action-button"; +import AddIcon from "@mui/icons-material/Add"; +import { useState } from "react"; +import MessageDialog from "./message-dialog"; function Component() { const i18n = useI18n(); + + const [open, setOpen] = useState(false); + const tableOptions = { recordType: ["admin", NAMESPACE], columns: [ { name: "created_at", label: i18n.t("messages.attributes.created_at") }, { name: "body", label: i18n.t("messages.attributes.body") } ], - onTableChange: fetchMessages, + onTableChange: fetchMessages }; + + const newMessageButton = ( + } + text="buttons.new" + type={ACTION_BUTTON_TYPES.default} + onClick={() => setOpen(true)} + /> + ); return ( - + {newMessageButton} + diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx new file mode 100644 index 0000000000..3f000f52dd --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx @@ -0,0 +1,13 @@ +import PropTypes from "prop-types"; +import ActionDialog from "../../../../action-dialog"; + +function Component({open=false}) { + return

Hello world

+} + + +// TODO proptypes +Component.propTypes = {open: PropTypes.bool}; +Component.displayName = "MessageDialog"; + +export default Component; \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/index.js b/app/javascript/components/pages/admin/messages-list/message-dialog/index.js new file mode 100644 index 0000000000..b6e0586481 --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/index.js @@ -0,0 +1 @@ +export { default } from "./component"; From 9051b14c507bbff1a3fbb6600a425420919dee23 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Wed, 23 Oct 2024 18:37:05 +0100 Subject: [PATCH 07/14] Add form in dialog handler --- .../pages/admin/messages-list/component.jsx | 6 ++++- .../message-dialog/component.jsx | 16 +++++++++--- .../message-dialog/formSections.js | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx index 3171640cd4..7e26a9c4d1 100644 --- a/app/javascript/components/pages/admin/messages-list/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -24,6 +24,10 @@ function Component() { onTableChange: fetchMessages }; + const closeDialog = () => { + setOpen(false); + }; + const newMessageButton = ( } @@ -35,7 +39,7 @@ function Component() { return ( {newMessageButton} - + diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx index 3f000f52dd..99c702a424 100644 --- a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx @@ -1,13 +1,21 @@ import PropTypes from "prop-types"; import ActionDialog from "../../../../action-dialog"; +import Form from "../../../../form"; +import form from "./formSections"; +import { useI18n } from "../../../../i18n"; -function Component({open=false}) { - return

Hello world

+function Component({open, onClose}) { + const i18n = useI18n(); + return +
+ } - // TODO proptypes -Component.propTypes = {open: PropTypes.bool}; +Component.propTypes = {open: PropTypes.bool, onClose: PropTypes.func}; Component.displayName = "MessageDialog"; export default Component; \ No newline at end of file diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js new file mode 100644 index 0000000000..05c96975b3 --- /dev/null +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js @@ -0,0 +1,25 @@ +import { fromJS } from "immutable"; +import { FieldRecord, FormSectionRecord, TEXT_AREA, TEXT_FIELD } from "../../../../form"; + +const form = i18n => + fromJS([ + FormSectionRecord({ + unique_id: "message", + fields: [ + FieldRecord({ + name: "recipient", + display_name: i18n.t("messages.attribute.recipient"), + required: true, + autoFocus: true, + type: TEXT_FIELD + }), + FieldRecord({ + name: "body", + display_name: i18n.t("messages.attributes.body"), + required: true, + type: TEXT_AREA + }) + ] + }) + ]); +export default form; From bea2114d0c16d137b3be6e306f2aeab349491251 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Wed, 23 Oct 2024 18:53:18 +0100 Subject: [PATCH 08/14] Add some initial english strings for messages work --- config/locales/en.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index e71944d0ef..14761eba3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4536,3 +4536,8 @@ en: body: one: Do you want to allow Primero to send you notifications? Remember that you will continue to receive notifications on this device %{count} day after you log out. This is not recommended for shared devices. other: Do you want to allow Primero to send you notifications? Remember that you will continue to receive notifications on this device %{count} days after you log out. This is not recommended for shared devices. + messages: + attributes: + created_at: Created At + body: Body + send_message: Send Message From db925e70b327584d985c5b84d82e235ce210e325 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Wed, 23 Oct 2024 19:05:00 +0100 Subject: [PATCH 09/14] Fix i18n issues with conflicting messages key --- .../components/pages/admin/messages-list/component.jsx | 4 ++-- .../pages/admin/messages-list/message-dialog/component.jsx | 2 +- .../pages/admin/messages-list/message-dialog/formSections.js | 2 +- config/locales/en.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx index 7e26a9c4d1..78fb43b96d 100644 --- a/app/javascript/components/pages/admin/messages-list/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -18,8 +18,8 @@ function Component() { const tableOptions = { recordType: ["admin", NAMESPACE], columns: [ - { name: "created_at", label: i18n.t("messages.attributes.created_at") }, - { name: "body", label: i18n.t("messages.attributes.body") } + { name: "created_at", label: i18n.t("rp_messages.attributes.created_at") }, + { name: "body", label: i18n.t("rp_messages.attributes.body") } ], onTableChange: fetchMessages }; diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx index 99c702a424..4d4df06f01 100644 --- a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx @@ -6,7 +6,7 @@ import { useI18n } from "../../../../i18n"; function Component({open, onClose}) { const i18n = useI18n(); - return + return }), FieldRecord({ name: "body", - display_name: i18n.t("messages.attributes.body"), + display_name: i18n.t("rp_messages.attributes.body"), required: true, type: TEXT_AREA }) diff --git a/config/locales/en.yml b/config/locales/en.yml index 14761eba3f..3a05c5d84b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4536,7 +4536,7 @@ en: body: one: Do you want to allow Primero to send you notifications? Remember that you will continue to receive notifications on this device %{count} day after you log out. This is not recommended for shared devices. other: Do you want to allow Primero to send you notifications? Remember that you will continue to receive notifications on this device %{count} days after you log out. This is not recommended for shared devices. - messages: + rp_messages: attributes: created_at: Created At body: Body From 0ee0de029158173c155bc8dd05ea6624c0ff0ac4 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 24 Oct 2024 14:55:48 +0100 Subject: [PATCH 10/14] WIP actually submit the form to the server Still need validation and various other things and authz on the server as well as some recipients selection that makes sense --- app/controllers/api/v2/messages_controller.rb | 10 +++++++ .../message-dialog/component.jsx | 27 ++++++++++++------- .../api/v2/messages/create.json.jbuilder | 3 +++ config/routes.rb | 2 +- 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 app/views/api/v2/messages/create.json.jbuilder diff --git a/app/controllers/api/v2/messages_controller.rb b/app/controllers/api/v2/messages_controller.rb index f67bd4de67..ebe049a0b2 100644 --- a/app/controllers/api/v2/messages_controller.rb +++ b/app/controllers/api/v2/messages_controller.rb @@ -4,6 +4,16 @@ class Api::V2::MessagesController < ApplicationApiController include Api::V2::Concerns::Pagination include Api::V2::Concerns::JsonValidateParams def index + # TODO: authz @messages = Message.all end + + def create + permitted = params.permit(:body) + @message = Message.new(permitted) + @message.save + # status = params[:data][:id].present? ? 204 : 200 + status = 204 + render :create, status: + end end diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx index 4d4df06f01..318c3df986 100644 --- a/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/component.jsx @@ -3,19 +3,28 @@ import ActionDialog from "../../../../action-dialog"; import Form from "../../../../form"; import form from "./formSections"; import { useI18n } from "../../../../i18n"; +import { useDispatch } from "react-redux"; +import { fetchMessages, saveMessage } from "../action-creators"; -function Component({open, onClose}) { - const i18n = useI18n(); - return - +const FORM_ID = "messages-form"; + +function Component({ open, onClose, submit }) { + const i18n = useI18n(); + const dispatch = useDispatch(); + + const handleSubmit = (message) => { + dispatch(saveMessage({message})) + onClose(); + } + return ( + + + ); } // TODO proptypes -Component.propTypes = {open: PropTypes.bool, onClose: PropTypes.func}; +Component.propTypes = { open: PropTypes.bool, onClose: PropTypes.func, submit: PropTypes.func }; Component.displayName = "MessageDialog"; -export default Component; \ No newline at end of file +export default Component; diff --git a/app/views/api/v2/messages/create.json.jbuilder b/app/views/api/v2/messages/create.json.jbuilder new file mode 100644 index 0000000000..5b2cf90dae --- /dev/null +++ b/app/views/api/v2/messages/create.json.jbuilder @@ -0,0 +1,3 @@ +json.data do + json.partial! 'api/v2/messages/message', message: @message +end diff --git a/config/routes.rb b/config/routes.rb index 25fb03887e..79fd7d74a1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -138,7 +138,7 @@ resources :key_performance_indicators, path: :kpis, only: [:show] resources :codes_of_conduct, only: %i[index create], controller: 'codes_of_conduct' resources :activity_log, only: [:index] - resources :messages, only: [:index] + resources :messages, only: %i[index create] resources :managed_reports, only: %i[index show] do collection do get :export, to: 'managed_reports#export' From 082abf55e489a5c3f860f6ea009a02df8fd62a0b Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Mon, 28 Oct 2024 15:15:45 +0000 Subject: [PATCH 11/14] Add usergroup recipient logic (partially) This is still very much a WIP --- app/controllers/api/v2/messages_controller.rb | 2 +- .../pages/admin/messages-list/component.jsx | 2 ++ .../messages-list/message-dialog/formSections.js | 10 ++++++---- app/models/message.rb | 16 ++++++++++++++++ ...2603_create_join_table_messages_recipients.rb | 8 ++++++++ db/schema.rb | 7 ++++++- 6 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20241028142603_create_join_table_messages_recipients.rb diff --git a/app/controllers/api/v2/messages_controller.rb b/app/controllers/api/v2/messages_controller.rb index ebe049a0b2..7b125cbfe1 100644 --- a/app/controllers/api/v2/messages_controller.rb +++ b/app/controllers/api/v2/messages_controller.rb @@ -9,7 +9,7 @@ def index end def create - permitted = params.permit(:body) + permitted = params.permit(:body, recipient_groups: []) @message = Message.new(permitted) @message.save # status = params[:data][:id].present? ? 204 : 200 diff --git a/app/javascript/components/pages/admin/messages-list/component.jsx b/app/javascript/components/pages/admin/messages-list/component.jsx index 78fb43b96d..0b6d52fc32 100644 --- a/app/javascript/components/pages/admin/messages-list/component.jsx +++ b/app/javascript/components/pages/admin/messages-list/component.jsx @@ -20,6 +20,8 @@ function Component() { columns: [ { name: "created_at", label: i18n.t("rp_messages.attributes.created_at") }, { name: "body", label: i18n.t("rp_messages.attributes.body") } + // performed by + // recipients ], onTableChange: fetchMessages }; diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js index 9088882680..2da5a7ccbe 100644 --- a/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js @@ -1,5 +1,5 @@ import { fromJS } from "immutable"; -import { FieldRecord, FormSectionRecord, TEXT_AREA, TEXT_FIELD } from "../../../../form"; +import { FieldRecord, FormSectionRecord, OPTION_TYPES, SELECT_FIELD, TEXT_AREA, TEXT_FIELD } from "../../../../form"; const form = i18n => fromJS([ @@ -7,11 +7,13 @@ const form = i18n => unique_id: "message", fields: [ FieldRecord({ - name: "recipient", - display_name: i18n.t("messages.attribute.recipient"), + name: "recipient_groups", + display_name: i18n.t("messages.attribute.recipient_groups"), required: true, autoFocus: true, - type: TEXT_FIELD + type: SELECT_FIELD, + multi_select: true, + option_strings_source: OPTION_TYPES.USER_GROUP }), FieldRecord({ name: "body", diff --git a/app/models/message.rb b/app/models/message.rb index 28bdd9d601..bfaffc9314 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,2 +1,18 @@ class Message < ApplicationRecord + has_and_belongs_to_many :recipients, class_name: 'User', join_table: 'messages_recipients' + validates :body, presence: true + + attr_writer :recipient_groups + + before_create :materialize_recipients + + def materialize_recipients + recipient_users = Set.new + @recipient_groups ||= [] + @recipient_groups.each do |group_uid| + group = UserGroup.find_by(unique_id: group_uid) + recipient_users.add(group.users) + end + recipients << recipient_users.to_a + end end diff --git a/db/migrate/20241028142603_create_join_table_messages_recipients.rb b/db/migrate/20241028142603_create_join_table_messages_recipients.rb new file mode 100644 index 0000000000..ccabf1485d --- /dev/null +++ b/db/migrate/20241028142603_create_join_table_messages_recipients.rb @@ -0,0 +1,8 @@ +class CreateJoinTableMessagesRecipients < ActiveRecord::Migration[6.1] + def change + create_join_table :messages, :users, table_name: 'messages_recipients' do |t| + # t.index [:message_id, :user_id] + # t.index [:user_id, :message_id] + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4fa5a67ef0..f29f8a81de 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_09_26_133159) do +ActiveRecord::Schema.define(version: 2024_10_28_142603) do # These are extensions that must be enabled in order to support this database enable_extension "ltree" @@ -393,6 +393,11 @@ t.datetime "updated_at", precision: 6, null: false end + create_table "messages_recipients", id: false, force: :cascade do |t| + t.bigint "message_id", null: false + t.bigint "user_id", null: false + end + create_table "perpetrators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.jsonb "data", default: {} t.index ["data"], name: "index_perpetrators_on_data", using: :gin From 38a9dcb47e57a6d37a8a4e80b8324d5dd1bcd374 Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 31 Oct 2024 13:47:58 +0000 Subject: [PATCH 12/14] Fix i18n strings for rp_messages modal --- .../pages/admin/messages-list/message-dialog/formSections.js | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js index 2da5a7ccbe..39a3a294b3 100644 --- a/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js +++ b/app/javascript/components/pages/admin/messages-list/message-dialog/formSections.js @@ -8,7 +8,7 @@ const form = i18n => fields: [ FieldRecord({ name: "recipient_groups", - display_name: i18n.t("messages.attribute.recipient_groups"), + display_name: i18n.t("rp_messages.attributes.recipient_groups"), required: true, autoFocus: true, type: SELECT_FIELD, diff --git a/config/locales/en.yml b/config/locales/en.yml index 3a05c5d84b..6ecd4f25f5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4540,4 +4540,5 @@ en: attributes: created_at: Created At body: Body + recipient_groups: Recipient User Groups send_message: Send Message From e6f11074b7569e62de0e6afbc8d6d602faf30aee Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 31 Oct 2024 14:24:00 +0000 Subject: [PATCH 13/14] Add more scaffolding - we should be able to send messages now --- app/jobs/send_rapidpro_messages_job.rb | 12 ++++++++++++ app/models/message.rb | 6 ++++++ app/services/api_connector/rapidpro_connector.rb | 4 ++++ app/services/rapidpro_connector_service.rb | 9 +++++++++ 4 files changed, 31 insertions(+) create mode 100644 app/jobs/send_rapidpro_messages_job.rb create mode 100644 app/services/rapidpro_connector_service.rb diff --git a/app/jobs/send_rapidpro_messages_job.rb b/app/jobs/send_rapidpro_messages_job.rb new file mode 100644 index 0000000000..f946aa9ff4 --- /dev/null +++ b/app/jobs/send_rapidpro_messages_job.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SendRapidproMessagesJob < ApplicationJob + queue_as :mailer + def perform(message_id) + message = Message.find(message_id) + recipients = message.recipients + valid_urns = recipients.where.not(rapidpro_urn: nil).where.not(rapidpro_urn: '') + connector = RapidproConnectorService.instance + connector.send_message_bulk(valid_urns, message.body) + end +end diff --git a/app/models/message.rb b/app/models/message.rb index bfaffc9314..ae3136174e 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -6,6 +6,8 @@ class Message < ApplicationRecord before_create :materialize_recipients + after_create :send_message + def materialize_recipients recipient_users = Set.new @recipient_groups ||= [] @@ -15,4 +17,8 @@ def materialize_recipients end recipients << recipient_users.to_a end + + def send_message + SendRapidproMessagesJob.perform_later(id) + end end diff --git a/app/services/api_connector/rapidpro_connector.rb b/app/services/api_connector/rapidpro_connector.rb index ac1ab2dec2..d33f039eb6 100644 --- a/app/services/api_connector/rapidpro_connector.rb +++ b/app/services/api_connector/rapidpro_connector.rb @@ -22,6 +22,10 @@ def send_message(urn, text) connection.post('/api/v2/broadcasts.json', post_params(urn, text)) end + def send_message_bulk(urns, text) + connection.post('/api/v2/broadcasts.json', { urns:, text: }) + end + def post_params(urn, text) { urns: [urn], diff --git a/app/services/rapidpro_connector_service.rb b/app/services/rapidpro_connector_service.rb new file mode 100644 index 0000000000..c6bea190cf --- /dev/null +++ b/app/services/rapidpro_connector_service.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class RapidproConnectorService + class << self + def instance + @instance || ApiConnector::RapidproConnector.build_from_env.freeze + end + end +end From 3024fd272dc5539ef530bfa869e31ff32652a96a Mon Sep 17 00:00:00 2001 From: Awen Saunders Date: Thu, 31 Oct 2024 14:58:03 +0000 Subject: [PATCH 14/14] Actually allow setting rapidpro urn in ui and fix getting urn --- app/jobs/send_rapidpro_messages_job.rb | 2 +- app/models/user.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/jobs/send_rapidpro_messages_job.rb b/app/jobs/send_rapidpro_messages_job.rb index f946aa9ff4..02082e59ac 100644 --- a/app/jobs/send_rapidpro_messages_job.rb +++ b/app/jobs/send_rapidpro_messages_job.rb @@ -5,7 +5,7 @@ class SendRapidproMessagesJob < ApplicationJob def perform(message_id) message = Message.find(message_id) recipients = message.recipients - valid_urns = recipients.where.not(rapidpro_urn: nil).where.not(rapidpro_urn: '') + valid_urns = recipients.where.not(rapidpro_urn: nil).where.not(rapidpro_urn: '').map(&:rapidpro_urn) connector = RapidproConnectorService.instance connector.send_message_bulk(valid_urns, message.body) end diff --git a/app/models/user.rb b/app/models/user.rb index 04926f37ea..5901c700ac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -29,6 +29,7 @@ class User < ApplicationRecord 'password_reset' => { 'type' => 'boolean' }, 'role_id' => { 'type' => 'string' }, 'agency_office' => { 'type' => %w[string null] }, 'code_of_conduct_id' => { 'type' => 'integer' }, 'send_mail' => { 'type' => 'boolean' }, 'receive_webpush' => { 'type' => 'boolean' }, + 'rapidpro_urn' => { 'type' => 'string' }, 'settings' => { 'type' => %w[object null], 'properties' => { 'notifications' => {