Support/Notifications: Difference between revisions

category -> Support Archive
(category -> Support Archive)
 
(2 intermediate revisions by 2 users not shown)
Line 47: Line 47:
If a user registers, we should look for any anonymous watches with that email address and add them to the user, when the user is confirmed.
If a user registers, we should look for any anonymous watches with that email address and add them to the user, when the user is confirmed.


= Evolution of table structure =
= Schedule =
A strawman schedule—when to target having things up for review:
* 1/24: fire() offthreading (up for review)
* 1/26: object_id moving; de-duplication
* 1/28: templating; confirmation views
* 2/1: claiming; porting old client code & deleting old subsystem code (which go together)
* 2/4: migrating old watches


== Iteration 1: Event type determines the format of the path column(s). ==
That should be possible with 2 people on it; we have between 3 and 4. Have fun!
path        path2*    type**                      email/whatever
15                      DocumentEvent              whatever@whatever.com
15.en-US                DocumentEvent
15.de.2837              DocumentEvent
15.en-US.30            DocumentSignificanceEvent
                        TagEvent
* optional. Choose the split per event type based on where orthogonality happens.
** An event type, which can span, frex, use cases 1-3 below. Determines the use of the spec fields.


 
[[Category:Support Archive]]
== Iteration 2: Get ref-integ on content type. ==
content_type    object_id    path      event_type                email/?  (usecase)
15                          en-US    DocumentLocaleEvent                2
15                                    DefaultEvent                        1
15              2837                  DefaultEvent                        3
15                          en-US.30  DocumentLocaleSignifEvent          7
15                          de.L.A    UntranslatedEvent                  5
 
Don't make path col unicode: save space.
 
 
== Iteration 3: More normalization, yielding better queryability: orthogonality of path elements (not just hierarchy) is possible without ridiculously inefficient substring queries. I don't think the joins would be too bad, but benches would be nice. Maintains ref integ of content type and object. ==
watches:
id content_type object_id event_type        user/email/whatever
1  15          2345      UntranslatedEvent
 
watch_elements:
watch_id name        value (int for size/speed/ad hoc joins)
1        lang        CRC32(de)  # Postgres doesn't have a CRC32 function, so ad hoc joins on string keys would be a pain.
1        localizable 1
1        approved    1
 
on UntranslatedEvent for object_id=2345 and content_type=15:
    select distinct email from watches
    left join watch_elements lang on watches.id=lang.watch_id and name='LANG' and (value IS NULL or value='de')
    left join watch_elements localizable on watches.id=localizable.watch_id and name='LOCA' and (value IS NULL or value='1')
    left join watch_elements approved on watches.id=approved.watch_id and name='APPR' and (value IS NULL or value='1')
    where content_type=15 and (object_id=2345 or object_id IS NULL) and event_type=UntranslatedEvent
    # It's an (n+1)-table join where n is the max length of the path (excluding content type and object ID). n of the joins, however, would be selecting among the same n rows: not a big deal.
 
 
== Iteration 4: Move content_type and object_id into watch_elements? See what the benches say. ==
watches:
id event_type        user/email/whatever
1  UntranslatedEvent
watch_elements:
watch_id    name        value (int for size/speed/ad hoc joins)
1            lang        CRC32(de)  # Postgres doesn't support CRC32 in the DB, so ad hoc joins on string keys would be a drag.
1            localizable  1
1            approved    1
1            content_type 15
1            object_id    2345     
 
This will bring it up to an (n+1)-table join, n being the max length of a certain event type's path *including* content type and object ID.
 
: Don't hamper work now because of Postgres. Assume we're on MySQL until Oracle shows up with a bill.
 
= Features =
* Maybe blocking an addy someday till a mail is confirmed (to prevent spamming)?
* Daily digests, supported by 2 tables:
digests:
user    event_type
digest_elements (values plugged into the template specified by event_type):
name      value
id        8634
hat_color red
 
= API Sketch =
class ObjectModifiedWatchType(object):
    """A notification which fires on the modification of a specific object"""
    code = 'modi'  # Key for the event_type column. Should be same size as a 32-bit int (but more memorable) if we store as a binary char(4). Migrations will be easy in case of collisions. All-lowercase codes are reserved for the notification app itself.
    @classmethod
    def fire(cls, obj):
        """Signal listener which sends any appropriate notifications"""
    @classmethod
    def create(cls, object):
        """Create (and save, and return) a watch which fires on the modification of the given object."""
    ...
objectModifiedSignal.register(ObjectCreatedEvent.listener)  # modulo spelling
class ContentTypeModifiedWatchType(...)
    code = 'modi'  # Can share a key with the above since the NULL in the object_id column and the non-NULL in the content_type_id column are sufficient to distinguish them.
class ContentTypeCreatedWatchType(...)
    code = 'crea'
class LocaleCreationWatchType(...):  # abstract because it doesn't provide a template. Might not really be worth making this abstract. We don't have a lot of locale-having things atm.
    """A notification which fires when an object (like a wiki Document) is created with a given locale.
   
    Works with anything that has a `locale` attr.
   
    """
    code = 'WLCr'  # convention: capital letters start a new word
   
    @classmethod
    def create(cls, locale, content_type=None):
        """Watch type for the creation of any object of a given locale.
        Specify a content_type to limit to a particular type.
        """
        watch = Watches.create(content_type=content_type, object_id=None, event_type=cls.code, user=[figure out how to represent this relationship to support anonymous also])
       
        # WatchElements implements a key/value store. We might use 4-char codes as the keys, again because they're more memorable than ints and because they have to be unique only within an event_type. Thus, none are reserved.
        WatchElements.create(watch=watch, name='locl', value=crc32(locale))
    @classmethod
    def fire(cls, obj):
        # TODO: Can probably use the ORM for this:
        watches = execute("""select distinct user (or whatever) from watches
            inner join watch_elements locale on watches.id=lang.watch_id and name='locl' and value={obj.locale}
            where content_type IS NULL or content_type={obj.content_type}
            AND event_type={cls.code}""")
        for w in watches:
            cls.notify(obj)  # builds and sends one mail, for example
    @classmethod
    def notify(cls, obj):
        # Send a mail or what have you.
        raise NotImplementError
class WikiLocaleCreationWatchType(LocaleCreationWatchType):
    @classmethod
    def notify(cls, obj):
        send_mail_in_a_task(some_template.render(obj.this, obj.that, ...))
objectCreatedSignal.register(WikiLocaleCreationWatch.fire)
1,268

edits