Skip to content

electionObj data schema

David edited this page Jun 29, 2022 · 8 revisions

Terminology:

[note: this may not yet be consistent in the code, but we will refactor]

  • electionDoc - the mongodb document where webComponent.webComponent==="ElectionDoc"
  • electionObj - the webComponent of an electionDoc with other doc's "joined" in, as object of objects

objectOfobject's

Using this structure, rather than an array[], makes it easy to incrementally merge and delete items without confusion.

objectOfObjects: { 
    "id1": {...},
    "id2": {...},
    ...
} 

where ids are either integers 0, 1, 2, ... or they are Mongo ObjectID().toString()'s.

setOrDelete(objectOfObjects, {id0: {...}})

Will add a new entry to objectOfObjets.

setUnset(objectOfObjects, {id2: {...}})

will modify the existing entry.

setUnset(objectOfObjects: {id1: undefined})

will delete the item, id1.

1 to find the latest item, use:

import getLatestIota from '../app/lib/get-election-status-methods

const latestInvitationObj=getLatestIota(electionObj.moderator.invitations)

Iota

a mongodb model for the iotas collection where every document has a basic schema including a

{
    _id: ObjectId,
    subject: string,
    description: string,
    parentId, ObjectId.toString(),
    webComponent, {webComponent: string, ...any},
    component: {component: string, ...any},
    bp_info: {...any},
    userId: ObjectId.toString()
}
  • component has a property 'component', which is expected to correspond to the name of a file in app/data-components. the object can have any other properties in it. Additional schema testing depend on the value of component.component.

  • webComponent has a property 'webComponent', which is string in ReactCase that is expected to correspond to the name of a file in app/web-components the object can have any other properties in it. Additional schema testing depends on the Component

  • bp_info - is a catch all for "ballot" info, and should be avoided in the future. But for now, depending on the viewer or recorder doc, has propertied like:

bp_info: { // if doc is a viewer
  office: name of office, like "President"
  election_date: in the form 2020-11-04
  election_list: list of all the viewer paths for each office in an election (for a viewer record)
  next_election: the next election path in the list
  prev_election: the previous election path in the list
  election_source: organization hosting the election
  // ballotpedia specific
  bp_info_alphabetize: true if candidates should be sorted by last name
  stage_id: unique id for the election/office/primary-genereal-runoff
  race: {} do not use 
}

bp_info: { // if doc is a recorder
  office: name of office, like "President"
  election_date: in the form 2020-11-04
  candidate_name: full, official name of candidate
  first_name: - mostly only used with ballotpedia
  last_name: last name (used when sorting by last name)
  unique_id: mongo ObjectID.toString
  candidate_emails: array of email addresses
  party: Name of party
  election_source: organization hosting the election
  // following are specific to ballotpedia data and should not be used
  candidate_stage_result_id: // the semi unique id when election data comes from ballotpedia
  person_emails: secondary array of email address when data comes from ballotpedia
  race: {} // don't use this
}

The definition for the electionObj that is used by other component to reference the content of an election setup.

function Component(props) {
  const {electionOM, ...}=props
  const [electionObj, electionMethods]=electionOM 
  ...
}

Whenever the content of electionObj is changed, electionOM will change, which will cause a rerender of the component Don't depend on electionObj or electionMethods to change to cause a re-render. The change may be deeper.

It might seem efficient for components to only take properties within electionObj - like questions, and that can be done like this:

// don't do this unless it's really needed
function Component(props){
    const [electionObj, electionMethods]=props
    return <Questions={electionObj.questions} >

But if the component is going to be using helper methods like electionMethods.areQuestionsLocked() that depends on other properties in the electionObj - so the Question component wouldn't rerender in this case. For this project the number of components that would get rerendered is reasonable so we won't worry about it for now. Later, if we need to, we can look at optimizing.

Tentative/draft/work-in-progress*

const   electionsObjs: [
        {
            _id: "mongoobjid",
            electionName: "U.S Presidential Election",
            organizationName: "United States Federal Government",
            electionDate: '2022-11-07T23:59:59.999Z',
            questions: {
               0:  {
                    text: "What is your favorite color?",
                    time: "30",
                },
                1:  {
                    text: "Do you have a pet?",
                    time: "60",
                },
                2:  {
                    text: "Should we try to fix income inequality?",
                    time: "90",
                },
            },
            script: {
               0:  {
                    text: "Welcome everyone. Our first question is: What is your favorite color?",
                },
                1:  {
                    text: "Thank you. Our next Question is: Do you have a pet?",
                },
                2:  {
                    text: "Great. And our last question is: Should we try to fix income inequality?",
                },
                3:  {
                    text: "Thanks everyone for watching this!",
                },
            },
            moderator: {
                invitations: { // not part of ElectionDoc, auto generated, may be empty or not present
                    62b7aef1642d482a345ece41: {
                        "_id": "62b7aef1642d482a345ece41,
                        "component": "ModeratorEmailSent",
                        "messageId": Any<String>,
                        "params": {
                            "email": "[email protected]",
                            "moderator": {
                                "email": "[email protected]",
                                "name": "bob",
                                "recorder_url": "localhost:3011/moderator-recorder",
                                "submissionDeadline": Any<String>,
                             },
                             "name": "admin name",
                             "organizationLogo": "https://www.bringfido.com/assets/images/bfi-logo-new.jpg",
                             "organizationName": "The Organization",
                        },
                        "sentDate": "2022-11-07T23:59:59.999Z",
                        "tags": Array [
                            "id:629950b73100ea171064d4b7",
                            "role:moderator",
                        ],
                        "templateId": Any<Number>,
                        "to": [
                             {
                                  "email": "[email protected]",
                                  "name": "bob",
                              },
                        ],
                      }
                   },
                  "recorders": { // not part of ElectionDoc, auto generated, may be empty or not present
                    "628d076dcf19df5aa438c07a": {
                      "_id": "628d076dcf19df5aa438c07a",
                      "bp_info": {
                        "office": "Moderator",
                      },
                      "component": {
                        "component": "undebateCreator",
                      },
                      "description": "Moderator Recorder for #4",
                      "parentId": "628c73daf2014b3f4c5da4ee",
                      "subject": "Moderator Recorder for #4",
                      "userId": "628d0a2afacbb605f4d8e6ac",
                      "path": "/some-moderator-specific-recorder-path",
                    },
                  },
                  "submissions": { // not part of ElectionDoc, auto generated, may be empty or not present
                    "628d2d25c945f836b8be0901": {
                      "_id": "628d2d25c945f836b8be0901",
                      "component": {
                        "component": "MergeParticipants",
                        "participant": {
                          "listening": "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510649/5d5b73c01e3b194174cd9b92-0-seat2.webm",
                          "name": "david",
                          "speaking": [
                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510654/5d5b73c01e3b194174cd9b92-1-speaking.webm",
                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510659/5d5b73c01e3b194174cd9b92-2-speaking.webm",
                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510665/5d5b73c01e3b194174cd9b92-3-speaking.webm",
                          ],
                        },
                      },
                      "description": "Moderator Recording for #4",
                      "parentId": "628d0b225f7a7746488c0bff",
                      "subject": "Moderator Recording for #4",
                      "userId": "628d0a2afacbb605f4d8e6ac",
                    },
                  },
                  "viewers": { // not part of ElectionDoc, auto generated, may be empty or not present
                    "628d0b225f7a7746488c0bff": {
                      "_id": "628d0b225f7a7746488c0bff",
                      "bp_info": {
                        "office": "Moderator",
                      },
                      "description": "Moderator Viewer for #4",
                      "parentId": "628c73daf2014b3f4c5da4ee",
                      "subject": "Moderator Viewer for #4",
                      "userId": "628d0a2afacbb605f4d8e6ac",
                      "webComponent": {
                        "webComponent": "CandidateConversation",
                      },
                      "path": "/some-moderator-specific-viewer-path",
                    },
                  },
                },
            candidates: {
                "61e76bbefeaa4a25840d85d0":
                      {
                          uniqueId: "61e76bbefeaa4a25840d85d0",
                          name: "Sarah Jones",
                          email: "[email protected]",
                          office: "President of the U.S.",
                          region: "United States",
                          invitations: { // not part of ElectionDoc, auto generated, may be empty or not present
                               // see moderator.invitations above
                          },
                          "recorders": { // derived data - list may be empty or not present
                              "628d076dcf19df5aa438c07a": {
                                  "_id": "628d076dcf19df5aa438c07a",
                                  "bp_info": {
                                       "office": "Moderator",
                                  },
                                  "component": Object {
                                       "component": "undebateCreator",
                                  },
                                  "description": "Moderator Recorder for #4",
                                  "parentId": "628c73daf2014b3f4c5da4ee",
                                  "subject": "Moderator Recorder for #4",
                                  "userId": "628d0a2afacbb605f4d8e6ac",
                               },
                           },
                          submissions: {  // derived data - list may be empty or not present
                              "628d2d25c945f836b8be0901": {
                                  "_id": "628d2d25c945f836b8be0901",
                                  "component": {
                                       "component": "MergeParticipants",
                                       "participant": {
                                           "listening": "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510649/5d5b73c01e3b194174cd9b92-0-seat2.webm",
                                       "name": "david",
                                       "speaking": Array [
                                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510654/5d5b73c01e3b194174cd9b92-1-speaking.webm",
                                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510659/5d5b73c01e3b194174cd9b92-2-speaking.webm",
                                            "https://res.cloudinary.com/hf6mryjpf/video/upload/v1566510665/5d5b73c01e3b194174cd9b92-3-speaking.webm",
                                       ],
                                   },
                                },
                                "description": "Moderator Recording for #4",
                                "parentId": "628d0b225f7a7746488c0bff",
                                "subject": "Moderator Recording for #4",
                                "userId": "628d0a2afacbb605f4d8e6ac",
                            },
                        },
                    }
                "61e76bfc8a82733d08f0cf12":
                    {
                        uniqueId: "61e76bfc8a82733d08f0cf12",
                        name: "Michael Jefferson",
                        email: "[email protected]",
                        office: "President of the U.S.",
                        region: "United States",
                        invitations: { // not part of ElectionDoc, auto generated, may be empty or not present
                            text: "Hi Mike, please send answers",
                            sentDate: candInviteSent,
                            responseDate: candInviteAccepted,
                            status: "Accepted",
                        },
                        submission: {// not part of ElectionDoc, auto generated, may be empty or not present
                        },
                    },
             },
            offices: {
               president: { viewer: {...}},
            },
            timeline: {
                moderatorDeadlineReminderEmails: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
                moderatorSubmissionDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                 },
                candidateDeadlineReminderEmails: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
                candidateSubmissionDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     }
                 },
                moderatorInviteDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
            },
            undebateDate: undebate,
        },
    ],
}

// these make it easier to create test electionObj's
const addDays = (date, days) => {
    return new Date(date.getTime() + days * 86400000)
}
const initialDate = new Date(2021, 11, 7)
const modInviteSentDate = addDays(initialDate, 2)
const modInviteDeadline = addDays(modInviteSentDate, 2)
const modInviteAcceptDate = addDays(modInviteDeadline, 2)
const modReminder1 = addDays(modInviteAcceptDate, 2)
const modReminder2 = addDays(modReminder1, 2)
const modFinal = addDays(modReminder2, 2)
const candInviteSent = addDays(modFinal, 2)
const candInviteAccepted = addDays(candInviteSent, 2)
const candReminder = addDays(candInviteAccepted, 2)
const candFinal = addDays(candReminder, 2)
const undebate = addDays(candFinal, 2)
const election = addDays(undebate, 2)
electionMethods={
    upsert: (obj)=>{}, 
    areQuestionsLocked: ()=>{},
    createNew: ()=>{},
    sendInvite: ()=>{},
    sendCandidateInvites: ()={},
    ...

Election Methods

upsert(obj)

  • obj - an object that only contains the portion of electionObj that needs to be changed.

When there is new input from the user, upsert is called to update the electionObj. It will be updated and cause a new version of electionOM to be generated, which will cause all components with it as a prop to rerender. Changes will also be accumulated and sent to the database in a timely manner.

Example:

electionMethods.upsert({moderator: {email: 'new email address', {name: 'name'})

// or if the user deletes something:

electionMethods.upsert({moderator: {email: undefined, name:  undefined})

areQuestionsLocked()

Returns true if Questions can not be changed - for example because invite has been sent to the moderator to record.

createNew()

Create a new, empty electionObj and navigate to the Configure Election page #23

sendInvite()

email an invitation to the moderator of the electionObj

sendCandidateInvites()

email invitations to the candidates in the electionObj.candidates