Spanish (Español) translation by Elías Nicolás (you can also view the original English article)



Este tutorial forma parte de la serie Creando su Startup Con PHP en Envato Tuts +. En esta serie, te estoy guiando a través del lanzamiento de un startup de concepto a realidad utilizando mi aplicación de Meeting Planner como un ejemplo de la vida real. Cada paso a lo largo del camino, voy a lanzar el código de Meeting Planner como ejemplos de código abierto que puede aprender. También me ocuparé de asuntos relacionados con negocios del startup de a medida que surjan.
En esta serie de dos partes, describiré cómo construimos la infraestructura de notificaciones y su entrega. Hoy, me centraré en el MeetingLog para realizar un seguimiento de los cambios que nos ayuden a determinar cuándo enviar actualizaciones.
Si aún no ha probado Meeting Planner, aproveche y programe su primera reunión. Participo en los hilos del comentario abajo, así que dígame lo que usted piensa! Me interesa especialmente si desea sugerir nuevas funciones o temas para futuros tutoriales.
Como recordatorio, todo el código para Meeting Planner está escrito en el framework Yii2 para PHP. Si desea obtener más información acerca de Yii2, consulte nuestra serie paralela Programación con Yii2.
Mi visión para las notificaciones
Esto es lo que normalmente ve un participante de la reunión cuando recibe una invitación. Comenzarán a compartir su disponibilidad para lugares y horas ya veces eligiendo las selecciones finales:



Cuando un participante de una reunión responde a una invitación como la anterior, pueden hacer un puñado de cambios. Por ejemplo:
- Esta fecha y hora funciona, estos dos no.a
- Este lugar funciona, éste no, vamos a considerar también este adicional.
- Elijo este lugar y esta horario.
- Añadir una nota: "Me alegro de que finalmente nos estamos reuniendo."
Después de hacer los cambios, necesitamos informar al organizador.
Nuestro objetivo es simplificar la planificación de reuniones y reducir el número de correos electrónicos involucrados. Si esperamos unos minutos después de que la persona haga cambios y consolidemos los cambios en una sola actualización, es probable que sea un buen momento para entregar la notificación. ¿Pero cómo construimos el código que hace eso? ¿Cómo hacemos un seguimiento de los cambios que estamos haciendo e ifnformar esto para el organizador?
Otra idea que tuve es eliminar la necesidad de hacer clic en enviar cuando haya terminado de hacer cambios. En primer lugar, esto requeriría una cierta educación al usuario para asegurarles que enviaremos sus cambios cuando hayan terminado. Pero también necesitábamos una manera de saber cuando estaba bien entregar los cambios al organizador.
Cómo funcionaran las notificaciones
Para restablecer y resumir los requisitos, veremos cómo funcionan las notificaciones:
Todas las acciones relacionadas con las reuniones se guardan en nuestro modelo MeetingLog. Pero necesitaremos una manera de crear un resumen textual del registro para compartir con los participantes. Es posible que desee mostrar sólo los cambios que se produjeron desde la última actualización. Así que tendremos que rastrear eso.
A medida que la persona realiza cambios en la reunión, indicamos visualmente que no es necesario hacer clic en un botón de envío o de cambios no existente.
Queremos supervisar si los cambios tienen unos minutos y consolidarlos en una sola actualización para el otro participante u organizador.
Y finalmente, tendremos que entregar la actualización por correo electrónico solo a los otros participantes, no a la que realizó los cambios. O bien, si ambos participantes han hecho cambios durante este tiempo, tendremos que notificar cada uno de los actos del otro.
Creación del registro de la reunión
Desde el principio, quería un registro completo de las acciones realizadas al planificar las reuniones por una serie de razones. Tal vez la gente quiera ver el historial de planificación, o volver a un cierto momento—pero también, es útil para la depuración. También resultó ser necesario para saber cuándo entregar actualizaciones sobre los cambios en la reunión.
Revisemos la estructura detrás del modelo MeetingLog. Aquí está la migración que crea la tabla:
class m141025_220133_create_meeting_log_table extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->createTable('{{%meeting_log}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'action' => Schema::TYPE_INTEGER.' NOT NULL', 'actor_id' => Schema::TYPE_BIGINT.' NOT NULL', 'item_id' => Schema::TYPE_INTEGER.' NOT NULL', 'extra_id' => Schema::TYPE_INTEGER.' NOT NULL', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_meeting_log_meeting', '{{%meeting_log}}', 'meeting_id', '{{%meeting}}', 'id', 'CASCADE', 'CASCADE'); $this->addForeignKey('fk_meeting_log_actor', '{{%meeting_log}}', 'actor_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); }
Los elementos esenciales que estamos guardando son:
- El
meeting_id
nos indica qué reunión estamos siguiendo. - La
action
nos dice lo que se hizo. - El
actor_id
nos dice quién realizó la acción (por ejemplo,user_id
). - El
item_id
sobrecargado puede representar un tiempo, un lugar una nota. - El extra_id es para guardar otra información dependiendo de la acción.
- Created_at nos indica cuándo se realizó la acción.
A continuación, definí constantes para todas las acciones como ACTION_SUGGEST_PLACE
o ACTION_ADD_NOTE
, etc.
class MeetingLog extends \yii\db\ActiveRecord { const ACTION_CREATE_MEETING = 0; const ACTION_EDIT_MEETING = 3; const ACTION_CANCEL_MEETING = 7; const ACTION_DELETE_MEETING = 8; const ACTION_DECLINE_MEETING = 9; const ACTION_SUGGEST_PLACE = 10; const ACTION_ACCEPT_ALL_PLACES = 11; const ACTION_ACCEPT_PLACE = 12; const ACTION_REJECT_PLACE = 15; const ACTION_SUGGEST_TIME = 20; const ACTION_ACCEPT_ALL_TIMES = 21; const ACTION_ACCEPT_TIME = 22; const ACTION_REJECT_TIME = 25; const ACTION_INVITE_PARTICIPANT = 30; const ACTION_ADD_NOTE = 40; const ACTION_SEND_INVITE = 50; const ACTION_FINALIZE_INVITE = 60; const ACTION_COMPLETE_MEETING = 100; const ACTION_CHOOSE_PLACE = 110; const ACTION_CHOOSE_TIME = 120; const ACTION_SENT_CONTACT_REQUEST = 150; const TIMELAPSE = 300; // five minutes
El método add facilita que la funcionalidad en todas partes de la aplicación registre la actividad en MeetingLog:
// add to log public static function add($meeting_id,$action,$actor_id=0,$item_id=0,$extra_id=0) { $log = new MeetingLog; $log->meeting_id=$meeting_id; $log->action =$action; $log->actor_id =$actor_id; $log->item_id =$item_id; $log->extra_id =$extra_id; $log->save(); // sets the touched_at field for the Meeting Meeting::touchLog($meeting_id); }
También agregé dos nuevos campos a la tabla de reunión: logged_at
y cleared_at
. Siempre
que se agreguen entradas de registro, la Reunión actualiza la marca de
tiempo logged_at
, mostrando el momento en que la reunión fue modificada
por última vez:
public static function touchLog($id) { $mtg = Meeting::findOne($id); $mtg->logged_at = time(); $mtg->update(); }
Por
ejemplo, cada vez que alguien añade una nueva opción de MeetingPlace,
un evento afterSave
se agrega al registro y, por supuesto, Meeting->logged_at
también se actualiza:
public function afterSave($insert,$changedAttributes) { parent::afterSave($insert,$changedAttributes); if ($insert) { // if MeetingPlace is added // add MeetingPlaceChoice for owner and participants $mpc = new MeetingPlaceChoice; $mpc->addForNewMeetingPlace($this->meeting_id,$this->suggested_by,$this->id); MeetingLog::add($this->meeting_id,MeetingLog::ACTION_SUGGEST_PLACE,$this->suggested_by,$this->place_id); } }
El tiempo logged_at
nos indica cuándo ocurrió el último cambio. La Meeting->cleared_at
time nos dirá la hora en que compartimos las últimas actualizaciones con los participantes. Por lo tanto, si logged_at
> cleared_at
, sabemos que los participantes no están completamente actualizados.
Creación de un resumen entendible



A continuación, construí métodos para ayudar a traducir el registro en una historia de idioma inglés de los cambios en la reunión.
Primero, creé getMeetingLogCommand() para obtener una descripción textual de la acción:
public function getMeetingLogCommand() { switch ($this->action) { case MeetingLog::ACTION_CREATE_MEETING: $label = Yii::t('frontend','create meeting'); break; case MeetingLog::ACTION_EDIT_MEETING: $label = Yii::t('frontend','edit meeting'); break; case MeetingLog::ACTION_CANCEL_MEETING: $label = Yii::t('frontend','cancel meeting'); break; case MeetingLog::ACTION_DELETE_MEETING: $label = Yii::t('frontend','cancel meeting'); break; case MeetingLog::ACTION_DELETE_MEETING: $label = Yii::t('frontend','deleted meeting'); break; case MeetingLog::ACTION_SUGGEST_PLACE: $label = Yii::t('frontend','add place'); break; case MeetingLog::ACTION_SUGGEST_TIME: $label = Yii::t('frontend','add time'); break; case MeetingLog::ACTION_ADD_NOTE: $label = Yii::t('frontend','add note'); break; case MeetingLog::ACTION_INVITE_PARTICIPANT: $label = Yii::t('frontend','Invite participant'); break; case MeetingLog::ACTION_ACCEPT_ALL_PLACES: $label = Yii::t('frontend','accept all places'); break; case MeetingLog::ACTION_ACCEPT_PLACE: $label = Yii::t('frontend','accept place'); break; case MeetingLog::ACTION_REJECT_PLACE: $label = Yii::t('frontend','reject place'); break; case MeetingLog::ACTION_ACCEPT_ALL_TIMES: $label = Yii::t('frontend','accept all times'); break; case MeetingLog::ACTION_ACCEPT_TIME: $label = Yii::t('frontend','accept time'); break; case MeetingLog::ACTION_REJECT_TIME: $label = Yii::t('frontend','reject time'); break; case MeetingLog::ACTION_CHOOSE_PLACE: $label = Yii::t('frontend','choose place'); break; case MeetingLog::ACTION_CHOOSE_TIME: $label = Yii::t('frontend','choose time'); break; case MeetingLog::ACTION_SEND_INVITE: $label = Yii::t('frontend','Send'); break; case MeetingLog::ACTION_FINALIZE_INVITE: $label = Yii::t('frontend','Finalize'); break; case MeetingLog::ACTION_COMPLETE_MEETING: $label = Yii::t('frontend','Complete meeting'); break; case MeetingLog::ACTION_SENT_CONTACT_REQUEST: $label = Yii::t('frontend','Send request for contact information'); default: $label = Yii::t('frontend','Unknown'); break; } return $label; }
Entonces, creé getMeetingLogItem(), que encuentra contextualmente la etiqueta apropiada del objeto basada en qué acción fue realizada. Todavía hay algunas notas de depuración para mí mismo aquí:
public function getMeetingLogItem() { $label=''; switch ($this->action) { case MeetingLog::ACTION_CREATE_MEETING: case MeetingLog::ACTION_EDIT_MEETING: case MeetingLog::ACTION_CANCEL_MEETING: case MeetingLog::ACTION_DECLINE_MEETING: $label = Yii::t('frontend','-'); break; case MeetingLog::ACTION_INVITE_PARTICIPANT: $label = MiscHelpers::getDisplayName($this->item_id); if (is_null($label)) { $label = 'Error - unknown user'; } break; case MeetingLog::ACTION_SUGGEST_PLACE: $label = Place::find()->where(['id'=>$this->item_id])->one(); if (is_null($label)) { $label = 'Error - suggested unknown place'; } else { $label = $label->name; if (is_null($label)) { $label = 'Error - suggested place has unknown name'; } } break; case MeetingLog::ACTION_ACCEPT_PLACE: case MeetingLog::ACTION_REJECT_PLACE: $label = MeetingPlace::find()->where(['id'=>$this->item_id])->one(); if (is_null($label)) { $label = 'Error - Accept or reject unknown place x1'; } else { if (is_null($label->place)) $label = 'Error Accept or reject unknown place x2'; else { $label = $label->place->name; if (is_null($label)) { $label = 'Error accept or reject unknown place name x3'; } } } break; case MeetingLog::ACTION_CHOOSE_PLACE: $label = MeetingPlace::find()->where(['id'=>$this->item_id])->one(); if (is_null($label)) { $label = 'Error - chose unknown place x1'; } else { if (is_null($label->place)) $label = 'Error chose unknown place x2'; else { $label = $label->place->name; if (is_null($label)) { $label = 'Error - choose unknown place name x3'; } } } break; case MeetingLog::ACTION_CHOOSE_TIME: case MeetingLog::ACTION_SUGGEST_TIME: case MeetingLog::ACTION_ACCEPT_TIME: case MeetingLog::ACTION_REJECT_TIME: // get the start time $mt = MeetingTime::find()->where(['id'=>$this->item_id])->one(); if (is_null($mt)) { $label = 'Error meeting time unknown'; } else { $label = Meeting::friendlyDateFromTimestamp($mt->start); } break; case MeetingLog::ACTION_ADD_NOTE: if ($this->item_id ==0) { $label = 'note not logged'; } else { $label = MeetingNote::find()->where(['id'=>$this->item_id])->one()->note; } break; case MeetingLog::ACTION_ACCEPT_ALL_PLACES: case MeetingLog::ACTION_ACCEPT_ALL_TIMES: case MeetingLog::ACTION_SEND_INVITE: case MeetingLog::ACTION_FINALIZE_INVITE: case MeetingLog::ACTION_COMPLETE_MEETING: case MeetingLog::ACTION_SENT_CONTACT_REQUEST: $label = Yii::t('frontend','-'); break; default: $label = Yii::t('frontend','n/a'); break; } return $label; }
Por
ejemplo, crear, editar, cancelar, eliminar la reunión no necesita
información del elemento, mientras que el tiempo de aceptación requiere
un item_id
específico que corresponde a una fecha y hora de
MeetingTimeChoice
.
Proporcionar pistas visuales
Cuando un participante cambia los controles deslizantes por lugares y horas para indicar sus preferencias, o si se permite, hace una elección final, quería indicar para ellos que su organizador de la reunión será notificado cuando hayan terminado. También quería que esto fuera discreto, como un alerta emergente.
Dado que muchos de estos cambios están relacionados con AJAX, el mensaje necesario para aparecer cerca de las acciones del ratón. En la ocasión en que se envía un cambio (como una nota nueva), la notificación puede aparecer en la parte superior de la página.
Los avisos de Flash de Yii son excelentes, pero sólo aparecen en la parte superior de las páginas. Por ejemplo, actualmente, agregar una nota requiere una actualización de página. La gente verá la sugerencia de notificación en la parte superior de la página después de publicar su nota:



Por lo tanto, por ahora, mientras nuestro diseño sigue en flujo, sólo he creado elementos ocultos de notificación de flash por encima de las áreas que muestran lugares y por encima de la zona que muestra los tiempos. Siempre que la gente realice cambios, verán la notificación cerca de la acción del ratón.



En el futuro, no mostraremos estas notificaciones a usuarios experimentados como ya lo sabrán.
Por lo tanto, si una persona hace un cambio en un momento o lugar, llamamos a displayNotifier():
// users can say if a time is an option for them $('input[name="meeting-time-choice"]').on('switchChange.bootstrapSwitch', function(e, s) { // set intval to pass via AJAX from boolean state if (s) state = 1; else state =0; $.ajax({ url: '$urlPrefix/meeting-time-choice/set', data: {id: e.target.id, 'state': state}, success: function(data) { displayNotifier('time'); refreshSend(); refreshFinalize(); return true; } }); }); JS;
Este código en /frontend/views/meeting/view.php garantiza que no vean las alertas repetidamente a través de una sesión. Utiliza variables de sesión para rastrear si ya se ha mostrado una sugerencia:
$session = Yii::$app->session; if ($session['displayHint']=='on' || $model->status == $model::STATUS_PLANNING ) { $notifierOkay='off'; $session->remove('displayHint'); } else { $notifierOkay='on'; } ?> <input id="notifierOkay" value="<?= $notifierOkay ?>" type="hidden"> <?php $script = <<< JS var notifierOkay; // meeting sent already and no page change session flash if ($('#notifierOkay').val() == 'on') { notifierOkay = true; } else { notifierOkay = false; } function displayNotifier(mode) { if (notifierOkay) { if (mode == 'time') { $('#notifierTime').show(); } else if (mode == 'place') { $('#notifierPlace').show(); } else { alert("We\'ll automatically notify the organizer when you're done making changes."); } notifierOkay=false; } }
¿Que sigue?



He descrito la visión que estoy tratando de crear con las acciones de planificación entre los participantes. En el siguiente tutorial, le mostraré cómo supervisamos el tiempo para saber cuándo y cómo entregar actualizaciones. Y te mostraré cómo entregamos las actualizaciones por correo electrónico, como la anterior. También voy a mostrarles cómo creamos un resumen textual de los cambios recientes que se hicieron.
Mientras espera, pruebe la función de notificaciones y programe su primera reunión. Además, le agradecería que compartiera su experiencia a continuación en los comentarios, y siempre estoy interesado en sus sugerencias. También puede contactarme directamente en Twitter @reifman.
Vea los próximos tutoriales en la serie Creando su inicio con PHP. Hay algunas características más grandes que vendran pronto.
Enlaces relacionados
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly