# Análisis de eventos ## Diagramas ### Sincronizar con Google ```plantuml user -> browser: Escribe la url de su equipco browser -> equipco: busca su subdominio respectivo equipco -> browser: devuelve la aplicación browser -> browser: Se ejecuta browser -> user: Muestra al usuario user -> browser: Click en Sincronizar con google calendar browser -> auth: llamada https a /google-auth con host y userId auth -> auth: Genera el link para autenticación en google auth -> browser: redirección al link generado para google browser -> google: visita el link seleccionado ingresa su contraseña y confirma permiso google -> browser: redirecciona a /choose-calendar/google?GOOGLECODE browser -> auth: /choose-calendar/google?GOOGLECODE auth -> browser: Entrega archivos del app para que seleccione calendario (auth_app) browser->auth: post /google-login con code y userId auth->auth: Obten refresh token y guardalo, obten los calendarios auth->equipco: Post api/google-login-success con userId equipco->equipco: Guarda logged y mail equipco->auth: ok auth-> browser: envío de calendarios browser-> user: Muestra la página cargada user-> browser: selecciona un calendario y hace click en guardar browser -> auth: set-google-calendar auth -> auth: Crea la subscripción al calendario y obtiene lista de eventos de 2 meses auth -> equipco: POST service-calendar-success con los nuevos eventos equipco -> equipco: Compara crea edita Eventos equipco -> auth: ok auth -> browser: ok browser -> equipco: hostname.equipco equipco -> browser: App browser -> browser: render browser -> user: Muestra página equip. ``` ### Desincronizar con Google ```plantuml user -> browser: Click en Desconectar con google browser -> equipco: Envía GOOGLE_LOGOUT equipco -> auth: POST google-logout con userId auth -> auth: logout auth -> equipco: ok equipco -> equipco: remove events from agenda, update projects, update events equipco -> browser: Envía C_GOOGLE_LOGOUT browser -> browser: Ejecuta los cambios browser -> user: Actualiza la vista ``` ### Events Endpoint **No se priorizó terminarlo debido a que va a ser modificado** ```plantuml start :Validación del userId; :Validación del body; if (bodyError) then (yes) #yellow:warn: Received a bad request body. Send 422; stop else (no) if (UserIdError) then (yes) #yellow:warn: Received a bad userId param. Send 422; stop else (no) :eventSyncQ(); partition eventSyncQ() :**await** getUser(agenda, outlook, google); note right Query to the DB end note if (user is logged in service?) then (yes) else (no) #AAAAAA:res.json ok: false, error: not synced with service; end endif :**await** GetPriorities(); note right Query to the DB end note :getTime(); :Filter events of the service; :getAttendeeEmailsFromCalendarEvents(); :**await** getUsersByServiceAccount(); note right Query to the DB end note :userAsDictByEventService(); :**await** getEventPrioritiesByServiceEventIds(); note right Query to the DB end note :getEventsUpdates(); if (newEvents) then (yes) :continue(); else (no) :res ok true, failedEvents; end endif :define eventReducer; :Filter newEvents action update & has snapshot; if(events with snapshot > 0) then(yes) :groupEventByItsProjectIndex(); :**await** projectsWithSnapshotEvents(); note right Query to the DB end note :**foreach** projectsWithSnapshotEvents updateProject(); note right Query to the DB, not awaiting end note endif :brandNewEvents; if(brandNewEvents > 0) then (yes) :groupEventByItsProjectIndex; :**await** projectsWithNewEvents; :**foreach** projectsWithNewEvents updtateProject(); note right Query to the DB, not awaiting end note else (no) endif :filter removed Events; :eventsToBeRemovedFromProjects(); if(eventsToBeRemovedFromProjects > 0) :projectsIdsWithDeletedEvents; :**await** projectsWithDeletedEvents; note right Query to the DB end note :**foreach** projectsWithDeletedEvents getItemsAndOrderOfProjectByItemsBatch() updateProject(); note right Query to the DB, not awaiting end note endif :processedNewEvents; :**foreach** processedNewEvents eventReducer(e); :**await** updateUser(agenda); :**foreach** processedNewEvents; :**foreach** userAttendee push to derivatedActions; :save derivatedActions for userAttendees; :**await** allPromises not awaited; :Sockets.sendUpdate(derivatedActions); :res.json ok: true failedEvents; end ``` - getEventsUpdates ```plantuml start :map dbEvents; partition "**foreach** calendarEvents" { :find calendarEvent in dbEvents; :find calendarEvent in nonScheduleDBEvents; :dbEvent = scheduleDBEvent || nonScheduleDBEvent; if(formatEvent(dbEvent)) then(no) :formatEvent(dbEvent); else (yes) :failedEvents.push(event,error); kill endif if(newEvent && datelimit > now) then(yes) if(dbEvent) then(yes) if(compare attributes) then(yes) if(recurring) then(yes) :updateEventsNewRecurrents = newEvent; endif :newEvents.push(newEvent, update); endif if(db.status !== canceled && eventsPrioritiesIds exists) then(yes) :splice dbEvent; endif else(no) :newEvents.push(newEvent); endif else(no) endif } :newEvents.map update newEvent if is recurring; if(eventsPrioritiesIds > 0) then (yes) :push event as deleted to processedNewEvents; endif :return processedNewEvents, failedEvents; end ``` ## Problemas - Web - **Acciones derivadas**: - Las acciones derivadas enviadas a los dispositivos de los usuarios invitados no agrega nueva información, y solo manda la info que ya tenía la BD. Solo en la información del owner envía el nuevo evento. - Api - **Problemas con el Formato de evento** - completedDate, confirmedAt están en la estructura principal, cuando el evento se cumple por cada atendee. - hadDurationAt930: Solo hay uno en la estructura principal, pero si tuvo o no duración depende de cada attendee. - isOrganizer: Hay uno en la estructura principal, pero fuera de los attendee no tiene sentido. - organizerUserId, responsibleId son redundantes - status y state se confunden. - timeInvested y timesInvested No tiene sentido que esté en la estructura principal - Hay un desafío claro en el feature de recurrencia y de interacción entre "acciones" por lo cual sería útil estructurar el objeto, con atributos que son modificados por una "entidad", por ejemplo: google_data, outlook_data y equip_data, para aislar las modificaciones "compatibles" ```javascript { "attendees": [ { "completedDate": null , "confirmedAt": 1620819550208 , "duration": 30 , "email": anibal.cerda@3s.cl, » "isOrganizer": false , "state": "confirmed" , "timeInvested": null , "timesInvested": [ ], "updatedDate": "2021-05-12" , "userId": "635b5c6a-4bd1-42b8-a9ef-0e2a49a0e043" } , { "completedDate": null , "confirmedAt": 1620777414166 , "duration": 30 , "email": ignacio.bolomey@3s.cl, » "isOrganizer": true , "state": "confirmed" , "timeInvested": null , "timesInvested": [ ], "updatedDate": "2021-05-12" , "userId": "e75c820a-9900-400f-8524-10b0dda35954" } , { "completedDate": null , "confirmedAt": null , "duration": 30 , "email": nacho.bolomey@equipco.app, » "isOrganizer": false , "state": "notConfirmed" , "timeInvested": null , "timesInvested": [ ], "updatedDate": "2021-05-12" , "userId": null } ] , "completedDate": null , "confirmedAt": 1620777414166 , "createdAt": 1615553494429 , "createdSnapshotId": null , "dateLimit": "2021-05-12" , "duration": 30 , "eventId": "6gqjgpb268sjcbb6cph3ib9k6cs3ab9ocgqjgb9gcpijcohi64o34pb26k_20210512T120000Z" , "hadDurationAt930": true , "iCalUId": 6gqjgpb268sjcbb6cph3ib9k6cs3ab9ocgqjgb9gcpijcohi64o34pb26k_R20210316T110000@google.com, » "id": "b19bf6b9-1b6f-4dae-a9b3-5818f0a9bf22" , "isOrganizer": true , "isPrivate": false , "link": https://www.google.com/calendar/event?eid=NmdxamdwYjI2OHNqY2JiNmNwaDNpYjlrNmNzM2FiOW9jZ3FqZ2I5Z2NwaWpjb2hpNjRvMzRwYjI2a18yMDIxMDUxMlQxMjAwMDBaIGlnbmFjaW8uYm9sb21leUAzcy5jbA, » "organizerUserId": "e75c820a-9900-400f-8524-10b0dda35954" , "originalCreatedAt": "2021-01-20T18:43" , "originalDateLimit": "2021-05-12T08:00" , "project": null , "recurringId": "6gqjgpb268sjcbb6cph3ib9k6cs3ab9ocgqjgb9gcpijcohi64o34pb26k_R20210316T110000" , "responsibleId": "e75c820a-9900-400f-8524-10b0dda35954" , "service": "google" , "snapshots": [ ], "state": "confirmed" , "status": "confirmed" , "timeInvested": null , "timeType": "td" , "timesInvested": [ ], "title": "Check in Nacho y Anibal" , "type": "Event" , "wasInHoyAt930": true , "wasInHoyPast930": true , "wasInTd": [ ] } ``` - **Esructura del código** - El código fue escrito sin ninguna estructura más que los endpoints que reciben los eventos. Esto es grave en terminos de mantención y resolución de bugs. - La lógica de eventos debiese estar ordenada al menos como una líbrería para interactuar con los eventos. - routehandlers /events, **se define la función eventReducer dentro de la lógica**, por lo que por cada llamada es necesario crear esta función. - routehandlers, La lógica de Eventos no se reutiliza, por lo que tenemos dos endpoint `events`, `serviceCalendarSuccess` que definen distintos métodos para incorporar los eventos. - **Desempeño**: - Cada vez que un usuario se sincroniza con google genera una subscripción a un calendario, y por ende cada vez que google reconozca un cambio en algún evento de ese calendario enviará la lista de 2 semanas desde hoy a equip. - Events: - Cada vez que le llega una llamada de algún usuario, recibe todos los eventos de dos semanas - Se llama al usuario, su agenda y su mail de google y outlook - Si el cliente está loggeado a ese servicio, se llaman a todas las prioridades de la agenda del usuario. - Se recorren todos los eventos que llegaron, guardando la lista de mails de los asistentes. - Basado en la lista de asistentes, se obtiene de esos usuarios: id, createdAt, google, outlook y la agenda. - Se crea un diccionario de attendees. (mail -> uid) - nonScheduleDBEvents: Se obtienen todas las prioridades con el id del evento. - Se ejecuta getEventsUpdates: - Se crea eventsPrioritiesIds array con ids de eventos de la bd. - Recorre los eventos traidos por servicio: - Find de los eventos de la BD el id del evento traido por el servicio - Find del evento traido por el servicio en nonScheduleDBEvents - Si encontró el evento en la BD o en nonScheduleDBEvents, asume que tiene un evento en la BD. - Se formatea el evento y se guarda en newEvent - Si el evento se pudo formatear y es para hoy o mayor, se compara el evento de la BD con el evento nuevo. - **problema** La comparación del evento de la BD con el de google, asigna updates a eventos que no han cambiado. La comparación está realizando incorrectamente, debido a la condición `newEvent.attendees.length` ## Relaciones con equip - web - Desde Agenda o leaderTable - PlanificationHistory - PlanificationList - SimpleList - SimpleCardWithSubtaskList - simpleCard - IndicatorsInfo -VariableInfo - Lists - PlanificationList - SimpleList - SimpleCardWithSubtaskList - SimpleCard - Schedule - AgendaList - sortableList - sortableElement - CardTaskWrapper - EventCard - Projects - projectList - sortableProjectList - ProjectElementsList - sortableList - sortableElement - ElementWrapper - sortableHandler - SimpleCardWithSubtaskList - simpleCard - handlers - events: Recibe los eventos de auth y los procesa generando acciones - outlookLoginSuccess: Confirmación de que el usuario se conectó a outlook. Guarda el email utilizado en outlook. - googleLoginSuccess: Confirmación de que el usuario se conectó a google. Guarda el email utilizado en gcalendar. - serviceCalendarSuccess: Luego de una sincronización satisfactoria, se utiliza este endpoint para incorporar los eventos del usuario. - calendarServiceLogout: Confirmación de que el usuario se desconectó correctamente y la eliminación de sus eventos - Acciones - Delete_calendar_event - D_DELETE_CALENDAR_EVENT - complete_event - D_COMPLETE_EVENT - set_project_event - D_SET_PROJECT_EVENT - confirm_attendance - D_CONFIRM_ATTENDANCE - outlook_logout - D_OUTLOOK_LOGOUT - google_logout - D_GOOGLE_LOGOUT - Otras derivadas - del routehandler de events y serviceCalendarSuccess: D_SYNC_CALENDAR_EVENTS - Consecuencias del cron - Los eventos se incluyen dentro del modelo de prioridades. - dateLimitMovement: Se modifica el timetype - longitudinalChanges (Tiene errores, los valores longitudinales son por cada persona, pero actualmente es una por evento calculada para el organizador) - resetCompletedEvents - Hay un evento que fue cumplido ayer pero en el que state sea distinto a completed (significa que ese evento fue primero cumplido y luego movido a otra fecha), si tiene snapshots y el confirmedAt tiene la misa fecha que el snapshot, define el completedDate como null y confirmedAt como merezca. - delete events form agenda - Nueva consecuencia que elimina los eventos que ya sucedieron. Los elimina de agendas, priority y se actualiza en oldEvents - Indicadores - Productividad variable c - Está erroneo el filtro de c1 de la variable `completedEvents`, ya que revisa si el evento fue cumplido usando el modelo anterior, no el de attendees. - Está erroneo el calculo de la variable c2 , ya que usa el modelo antiguo (confirmedAt de primer nivel), en vez de usar el nuevo modelo de attendees. - Librerías utilizadas - actionReducer/utils - Lib3S - completeAttendeeDataForEvent ## Relación con feature de reuniones de clarificación - Aclaraciones - Los usuarios ingresan a su agenda una nueva prioridad que tiene como atributo el id de un evento, pero se genera una prioridad por usuario. - ¿Cómo se envian los eventos de meeting en el estado? - Web - Acciones - CANCEL_CLARIFICATION_MEETING_EVENT - CONFIRM_CLARIFICATION_MEETING_PROPOSAL - GET_CLARIFICATION_MEETING_DETAILS - SEND_CLARIFICATION_MEETING_PROPOSAL - SEND_DRAFT_REQUEST - SEND_REQUEST - UPDATE_MEETING_LINK - UPDATE_REQUEST - UPDATE_REQUEST_DATE_LIMIT - UPDATE_CREATED_REQUEST_BY_RECEIVER - queries - getMeeting - getMeetingsWithIds - getUserMeetingProposals - insertMeeting - updateMeeting - Consecuencias del cron - updateRequestNotificationsFromClarificationMeeting - longitudinalChanges // actualmente no lo afecta - dateLimitMovement - resetCompletedEvents // actualmente no lo afecta - - Indicadores - Librerías utilizadas ---- ## Requisitos - No se eliminará información de los eventos. - Crear una tabla historial para los eventos - Cuando hago un cambio e un evento, lo actualizo en priorities y en oldEvents. - Cuando saco al ultimo attendee se elimina el attendee, se elimina de priority y se guarda en oldEvents. - Por cada llamada de auth (serviceEvents, serviceName, userId) - Priority - newEvents: Agregar los serviceEvents que no están en priorities, ingresando solo los attendees que están sincronizados. - updatedEvents: Actualizar los eventos que estaban en priority, pero que tienen diferencias con los serviceEvents - changedDataEvents: Eventos que modificaron su hora, fecha, título - deletedAttendeeEvents: eventos que eliminaron a algún sus attendees - addedAttendeeEvents: eventos que agregaron a attendees - cancelledEvents: Los eventos que están en priority pero no en serviceEvents, deben eliminar al userId como attendee de los eventos, Y si es el último attendee, eliminar el registro de priority. A la vez, actualizar el evento en oldEvents, agregando una nueva versión, si queda sin attendees, el estado del evento es cancelled. - OldEvents - newEvents: Agregar los serviceEvents que no están en priorities, ingresando solo los attendees que están sincronizados. - updatedEvents - Actualizar los eventos que estaban en priority en una nueva versión. - cancelledEvents - Actualizar los eventos en oldEvents, la última versión debe no tener attendees y debe ser marcado como cancelado, si tiene más de un attendee solo hay que eliminar el userId de attendees. - Agenda - Agregar newEvents en las agendas de cada attendees del evento. - Actualizar changedDataEvents en la agenda de cada attendee. // @TODO Revisar si se duplica el orden de la agenda en la api y en schedule de la agenda. [incertidumbre con respecto a cómo el componente schedule ordena sus elementos.] Si es que no necesito modificar el orden de la agenda, quizás es más eficiente el crear un índice para obtener todos los eventos a los cuales están invitados. - Eliminar los deletedAttendeeEvents en la agenda de los attendees eliminados - Agregar addedAttendeeEvents en la agenda de los attendees agregados - Eliminar los cancelledEvents de la agenda del usuario. - Project - Si los cancelledEvents quedan sin integrantes, eliminarlos de projects - Actualizar los changedDataEvents (aquí está el proceso de los snapshots) - Acciones derivadas - Usuarios con newEvents (evento y nuevo orden) - Usuarios con changedDataEvents (evento y nuevo orden) - Usuarios dentro de deletedAttendeeEvents (nuevo orden) - Usuarios dentro de addedAttendeeEvents (evento y nuevo orden) - Usuarios con cancelledEvents (nuevo orden) - // Cambios en proyectos no se notifican debido a que la próxima vez que los consulten se actualizarán. ## Pseudocodigo de /events y ServiceCalendarSuccess - joiValidation() - userIdError - bodyError - Q // Toda la ejecución posterior se ingresa dentro de este wrapper - const serviceEmails = await queries.getServiceEmail() - const userIds2ServiceEmail = getUserIds2ServiceEmail(serviceEmails) userIds2ServiceEmail= { userId: 'serviceEmail' } - if (userIds2ServiceEmail[userId]) // De estó depende la ejecución posterior - serviceEmail2UserIds = transformEmail2userIds(userIds2ServiceEmail) serviceEmail2UserIds = { serviceEmail = [userId1, userId2...] } - const formattedEvents = formatServiceEvents(serviceName, serviceEvents, serviceEmail2UserIds) - const dbEvents = await queries.getEventsById(serviceEvents.map(e=>e.eventId)) - const trackUserChanges = { userId: [ 'agenda': [] // last agenda (whit all modifications) 'added': [event], 'deleted': [event], 'updatedEvent': [event] // event was updated ] } - const trackProjectChanges = { projectId: [ 'updated': {event} 'order': [], 'items': [] ] } - const trackUserChanges[userId] = await queries.getAgendaEvents(userId) - const eventChanges = getEventChanges(formattedEvents, dbEvents, userAgendaEvents) => { newEvents: [{...event},{...event2},...] updatedEvents: [{...event, addedAttendees, deletedAttendees}], cancelledEvents: [{...event, deletedAttendees},...] // from dbEvents from the difference between userAgendaEvents and serviceEvents. The user will be removed. } - for each newEvent in eventChanges.newEvents - await queries.saveNewPriority(newEvent) - await queries.saveOldEvent(newEvent) - const newAttendeesUserId = getAttendees(newEvent) - for each newAttendee in newAttendeesUserId - const trackUserChanges[newAttendee.id] = trackUserChanges[newAttendee.id] || await queries.getAgenda(newAttendee.id) - trackUserChanges[newAttendee.id] = { agenda: addEventToAgenda(newEvent, trackUserChanges[newAttendee.id]['agenda']), added: [...trackUserChanges[newAttendee.id]['added'], newEvent], deleted: trackUserChanges[newAttendee.id]['deleted'] } - for each updatedEvent in eventChanges.updatedEvents - queries.saveUpdatedEventInPriorities - const trackProjectChanges[projectId] = queries.getProject(eventChanges.updatedEvents) - trackProjectChanges[projectId] = updateProjects(trackProjectChanges[projectId], eventChanges.updatedEvents) // updatedEvents - const affectedAttendeesIds = getAffectedAttendees(updatedEvent) // We need ids of attendees and deleted attendees of each updatedEvents - foreach user id affectedAttendeesIds - trackUserChanges[affectedAttendeeId] = trackUserChanges[affectedAttendeesId] || queries.getAgenda(affectedAttendeesId) // we need agendas from attendees and deletedAttendees of updated events, we may have some of them, we only need to call the rest. - foreach deletedAttendees - trackUserChanges[attendeeId] = deleteFromAgendas(attendeeId, trackUserChanges[attendeeId]) //deletedAttendees - foreach attendee - if (addedAttendees.has(attendee)) - trackUserChanges[attendeeId] = AddToAgenda(attendeeId, trackUserChanges[attendeeId]) // addedAttendees - trackUserChanges[attendeeId] = changeAgendasOrder(attendeeId, trackUserChanges[attendeeId]) // updatedEvents - updateCanceledEvents(eventChanges.cancelledEvents) - const affectedAttendeesIds = getAffectedAttendees(eventChanges.cancelledEvents) - foreach(affectedAttendeesIds) - trackUserChanges[affectedAttendeesId] = trackUserChanges[affectedAttendeesId] || queries.getMissingAttendeesAgendas(affectedAttendeesId) - foreach cancelledEvents - queries.saveCancelledEventsPriority - trackProjectChanges[projectId] = updateProject(trackProjectChanges[projectId], eventChanges.cancelledEvent) - foreach attendee - trackUserChanges[affectedAttendeesId] =deleteFromAgenda(trackUserChanges[affectedAttendeesId], cancelledEvent) // cancelledEvents attendees. - derivedActions = getDerivedActions(trackUserChanges, trackProjectChanges) - confirmAction = getConfirmAction(trackUserChanges[userId]) ## Formato de los eventos en BD Reglas: - Atributos que están en otras prioridades tb van en primer nivel - Campos que dependen de la información de google o outlook van en service - Frecuencia de uso amerita acercarlo al primer nivel - const event = { id, projectId: 'projectId', snapshots, // revisar como cambia con oldEvents type: 'Event', createdAt: // date title link, duration: 30 , dateLimit, // shortdate // revisar qué ocurre con los eventos de más de un día. service: { // organizerUserId, // Es un arreglo! ya que muchos usuarios pueden estar conectados. @todo ojo en la web. serviceName eventId, iCalUId, // Es redundante con eventID? cual es la diferencia originalCreatedAt, originalDateLimit, // para qué sirve recurringId, status: 'notConfirmed' }, attendees: { 'userId1' : { completedDate: null , confirmedAt: null , hadDurationAt930, // no solo se utiliza en prioritize_agenda ya que no se puede cambiar de día todavía isOrganizer: true , state: "notConfirmed", // defined by us. ('confirmed', 'completed') timeInvested: null , timesInvested: [ ], timeType: 'cs', updatedAt: timestamp // used in confirm_attendance action. updatedDate: "2019-02-27", userId: null wasInHoyAt930, // no solo se utiliza en prioritize_agenda ya que no se puede cambiar de día todavía wasInHoyPast930, // no solo se utiliza en prioritize_agenda ya que no se puede cambiar de día todavía wasInTd: [] // necesario para el calculo de productividad c2 según el formato, es un array. } } } ## assign_duration_ priority is used on events? ## Estructura del código /serviceEvents readme.md index.js getUserIds2ServiceEmail transformEmail2userIds formatServiceEvents getEventChanges utils.js ## ¿Cómo funcionan los snapshots? - Al crear el formato, en formatEvent, se utiliza la función llamada getDbEventChanges que devuelve un atributo llamado snapshot y createdSnapshotId. - En getDbEventChanges, - Se crea una array que si que contenga los dbEvent.snapshots se pasa a snapshots - Si existe un evento en la bd y se detecta un cambio en datelimit (el evento trae un datelimit distinto a el que se encontraba en la bd) y el evento tenía estado de completed, se hace push a snapshots con el evento de la base de dato, con un array vacío snapshots y el snapshotId (uuid). En los comentarios de ese código dice: the snapshotId is only used to keep track an order inside the project - Luego vuelve al routehandler y si existen eventos con crceatedSnapshotId calcula la constante projectsIdsWithSnapshotEvents al ejecutar la función groupEventByItsProjectIndex donde acumula estos eventos según su projectId - Luego llama a todos los proyectos que están en esos projectId y los define como projectsWithSnapshotEvents - Luego los recorre, para reducirlos, toma como valor inicial items y order de project y como función reductora entrega la función getProjectItemsAndOrderForNewSnapshotEvent con el id del evento, el createdSanpshotId el order y items acumumulado - La función getProjectItemsAndOrderForNewSnapshotEvent obtiene el eventId, snapshotId el order y items, y reemplaza el evento y su snapshot y devuelve items y order. - Luego se actualiza el proyecto. ## Decisiones pendientes - Queremos mantener los eventos aunque sean eliminados desde google o después de ser realizado?