Jump to content

Extension:Echo/Creating a new notification type

From mediawiki.org
This page is a translated version of the page Extension:Echo/Creating a new notification type and the translation is 100% complete.
Verze MediaWiki:
1.45
Následující pokyny platí pro MediaWiki 1.45 a novější. Tato verze zavedla několik nových funkcí a některé zastaralé funkce jako součást práce na vestavěném frameworku pro oznámení. Pro dlouhodobou podporu MediaWiki 1.43 a starší verze viz Extension:Echo/Creating a new notification type (1.43).

Notifikační systém umožňuje přihlášeným uživatelům přijímat upozornění a oznámení i z jiných instancí MediaWiki, pokud jsou do tohoto systému zapojeny. U všech projektů Wikimedie tak dostávají uživatelé oznámení téměř ze všech wikinách, provozovaných v rámci jejího clusteru. Ovšem tento notifikační systém mohou využívat i třetí strany, které do něj zapojeny nejsou. Tzv. "třetí strany" ho mohou používat v rámci jedné wiki, ale i celé skupiny vzájemně propojených wikin (a mít tak pro ně společný systém upozornění).

Video na kterém je demonstrováno, jak vytvořit nový typ upozornění, byl prezentován v roce 2017 v rámci Setkání vývojářů Wikimedie.

Tento systém umožňuje vývojářům rozšíření generovat nové typy oznámení, které pak mohou za splnění určitých podmínek dostávat ostatní uživatelé. Tento dokument popisuje jakým způsobem se takové oznámení tvoří, vysvětluje osvědčené postupy a upozorňuje na úskalí, kterým je při tom potřeba se vyhnout.

Jak notifikace fungují - úvod

Oznámení se obecně skládají ze dvou konceptů – události a prezentačního modelu. Událost ukládá detaily spojené s událostí a prezentační model definuje, jakým způsobem se má tato událost prezentovat v prostředí uživatelského rozhraní.

Událost

Třída Event definuje obecné události, shromažďuje informace o související stránce, uživateli a wiki a vkládá je do databáze. Události jsou obecné a reagovat lze na ně i několika různými způsoby, pokud jsou nadefinovány. Dejme tomu, že zmíníte 4 osoby, což vytvoří jednu událost, na kterou ale budou odkazovat (a přes 'trigger' svázaná) oznámení pro tyto čtyři zmíněné uživatele. Všechny tyto jejich individuální notifikace však budou odkazovat na jednu a tu samou událost.

Prezentační model

Každý typ notifikace tak vyžaduje svůj prezentační model. Způsob, který bude definovat, co se v oznámení zobrazí, kam se bude odkazovat a jaké další sekundární akce s tím mají být spojeny. Front-endové komponenty notifikačního systému (vyskakovací okno s oznámením a stránka Special:Notifications) tyto informace načtou a na jejich základě vygenerují příslušné oznámení.

Typ notifikace pokaždé vychází z výchozí definice ve třídě EchoEventPresentationModel. Tato třída definuje základní chování všech oznámení a každé podřízené oznámení ji nejprve musí rozšířit a následně přizpůsobit detaily svým potřebám.

Jak fungují události

Pokud chcete vytvořit nový typ oznámení, měli byste nejprve vyvolat příslušnou událost. Logický postup je následující:

  1. Pokud je to na místě, vytvoří váš kód událost prostřednictvím Event::create()
  2. Příslušná událost se uloží do databáze a příslušným uživatelům se následně začne zobrazovat odpovídající oznámení.
  3. Když si uživatelé vyžádají seznam oznámení z rozhraní API, systém vyhledá příslušný model prezentace a vytvoří data - název, primární odkaz, sekundární odkazy, příslušného uživatele atd.
  4. Ke zobrazení příslušného oznámení použije front-end následně uložená strukturovaná data.
Pro větší názornost jsme popis celého procesu mírně zjednodušili. Podrobné vysvětlení toho, jak to funguje v praxi, včetně popisu databázových tabulek (uživatelů, wiki, odkazů mimo wiki atd.), je přesahuje rámec tohoto tutoriálu.

Definice

Definice události

Definice událostí se dějí v proměnné extension.json nebo v obslužné rutině háčku BeforeCreateEchoEvent. Je třeba samostatně definovat typy oznámení (povinné), kategorie oznámení (volitelné) a ikony oznámení (volitelné).

Definování kategorie události

Uživatelé mají obecně povoleno zaškrtnout/zrušit zaškrtnutí možnosti dostávat upozornění na web nebo e-mail v předvolbách. Název kategorie se používá jako klíč a je tím, na co pak odkazuje definice události v parametru "category". Použitím NotificationCategories v extension.json musíte pro každou kategorii definovat titulní zprávu (krátká zpráva používaná jako popisek v Special:Preferences na kartě Oznámení a v dávkových e-mailech s oznámeními) a také můžete definovat popisnou zprávu (používá se pouze v nastaveních, může obsahovat podrobnější popis). Nezapomeňte tyto zprávy vytvořit ve vašich lokalizačních souborech.

Definování ikony oznámení

Upozornění se zobrazují s ikonami. Pokud ikona není nastavena, zobrazí se výchozí ikona. Pokud je ikona nová, musí být načtena a definována pomocí NotificationIcons v extension.json.

Definujte ikonu pomocí celé adresy URL ikony: "url": "//example.org/icon.svg"

Případně můžete použít cestu k souboru, která je relativní vzhledem k $wgExtensionAssetsPath : "path": "extensionPath/to/icon.svg"

Definování informací o události

Parametr Povinné? Popis
category Požadované Kategorie, do které tato akce patří.

To je důležité pro zobrazení na stránce Special:Preferences

section Požadované Definuje sekci, do které toto oznámení patří.

Existují dva typy, Alerts (varování) a Notices (oznámení). Upozornění jsou pro naléhavá oznámení, která by měla být vyřízena okamžitě, např. příspěvek na diskusní stránce uživatele. Oznámení jsou pro upozornění, která nemusí být vyřízena okamžitě, např. poděkování. Pro upozornění vložte 'alert'. Pro oznámení vložte 'message'

presentation-model Požadované Třída pro model prezentace pro toto zobrazení události
user-locators Volitelné Definuje funkce, které vytvářejí seznam uživatelů, kterým mají být oznámeny (volitelné, pokud se příjemce zadávají přímo ve volání Event::create()).

Obvykle se odkazuje na funkce z třídy UserLocator, například:

"user-locators": [
	[
		"MediaWiki\\Extension\\Notifications\\UserLocator::locateFromEventExtra",
		[
			"my-custom-recipients"
		]
	]
]
group Volitelné Seskupte jej s podobnými oznámeními.

Známé skupiny v jádru Echo jsou: 'positive', 'negative', 'interactive', 'neutral'. Pokud není zadán, použije se 'neutral'.

user-filters[Pitfalls 1] Volitelné Definuje funkce, které vytvářejí seznam uživatelů, kteří nemají být upozorněni, ve stejném formátu jako user-locators.
immediate Volitelné Zda použít frontu úloh nebo ne.

Boolean, výchozí je použití fronty úloh (tj. false)

Podrobnosti o vytvoření události

Když vytváříme a spouštíme událost, musíme také poskytnout podrobnosti:

Parametr Povinné? Popis
type Požadované Název události, klíč, který jsme použili k definování události
title Volitelné Související název, je-li relevantní. Když příjemce oznámení navštíví tuto stránku, oznámení je označeno jako přečtené. Pokud je tato stránka smazána, událost je pro všechny příjemce skryta.
agent Volitelné Uživatel, který je původcem tohoto oznámení (poznámka: Toto není uživatel, který je upozorněn!)
extra Volitelné Řada dalších definic pro Echo a model prezentace k použití
Běžné doplňkové klávesy
revid Volitelné Pokud je zadáno a revize je označena jako drobná úprava, událost přeskočí e-mailová oznámení o této události (pokud není povolena předvolba enotifminoredits (nejedná se o drobné úpravy)).

Způsobí, že některé podrobnosti události budou skryty, pokud je revize smazána.

target-page Volitelné Další ID stránek, které mají být zpracovány podobně jako parametr "title".
Event::RECIPIENTS_IDX Volitelné ID uživatelů, kteří by měli obdržet oznámení o této události.
jakýkoli jiný klíč Volitelné Vlastní klíče může váš prezentační model použít k zobrazení podrobností oznámení a lokátory/filtry uživatelů k definování příjemců oznámení.

Definice prezentačního modelu

Prezentační modely mají být velmi flexibilní, aby umožňovaly vytváření typů upozornění se specifickými displeji. Na rozdíl od definice a vytvoření události je prezentační model samostatnou třídou a vyžaduje jeho rozšíření a vytvoření metod specifických pro případ oznámení. Toto jsou definice základní třídy EchoEventPresentationModel:

Metoda Výchozí hodnota Popis
canRender[Pitfalls 2] true Definuje podmínky, za kterých může být toto oznámení poskytnuto.

Můžete například zkontrolovat, zda byla stránka odstraněna, a pokud ano, nezobrazovat oznámení. Běžné použití je, pokud oznámení kdekoli používá $this->title stránky (pro záhlaví zprávy nebo pro odkazy), musí canRender() zkontrolovat, zda je nastaveno $this->title.

getIconType Musí být přepsáno Definuje symbolický název ikony pro toto oznámení (jak je definováno v NotificationIcons v extension.json)
getHeaderMessage Zpráva založená na výchozím klíči notification-header-{$this->type} Definuje zprávu i18n pro záhlaví
getCompactHeaderMessage Pokud není definován, použije getHeaderMessage() Definuje zprávu i18n pro záhlaví v případech, kdy je oznámení "kompaktní" (například uvnitř balíčku nebo skupiny napříč wikinami).
getSubjectMessage Pokud není definován, použije getHeaderMessage() Definuje zprávu i18n pro předmět e-mailu upozornění
getBodyMessage false Definuje zprávu i18n pro tělo oznámení.

Volitelné.

getPrimaryLink Musí být přepsáno Definuje primární odkaz pro toto oznámení.

Pro definici primárního odkazu použijte strukturu ['url' => (string) url, 'label' => (string) link text (non-escaped)] ('label' se používá pro verze bez JavaScriptu)

Pokud neexistuje primární odkaz, vrátí false.

getSecondaryLinks[Pitfalls 3] [] Definuje sekundární akce

Struktura sekundárních odkazů

Informace o sekundárních odkazech umožňují oznámením specifikovat dílčí odkazy na akce, které rozšiřují hlavní primární odkaz pro oznámení. Například oznámení o nějaké úpravě revize může mít svůj primární odkaz ukazovat na stránku s rozdílem revize, ale také mít sekundární odkazy směřující na uživatelskou stránku uživatele, který provedl změnu, nebo odkaz na zobrazení stránky, která byla změněna.

Sekundární odkazy také umožňují akce řízené API (akce 'dynamic') pro akce jako 'stop watching a page' nebo jiné, které vyžadují, aby front-end požádal o požadavek AJAX na API přímo z oznámení. Další podrobnosti o dynamických akcích jsou mimo rozsah tohoto kurzu.

Obecná struktura sekundárních odkazů:

public function getSecondaryLinks() {
    return [
       [
            'url' => (string) url,
            'label' => (string) link text (non-escaped),
            'description' => (string) descriptive text (optional, non-escaped),
            'icon' => (bool|string) symbolic ooui icon name (or false if there is none),
        ],
    ];
);

Návod: Vytvoření nového typu oznámení

Řekněme, že máme rozšíření, které vede seznam uživatelů, kteří mají zájem o nové tituly vytvořené s konkrétními slovy. Rozšíření umožňuje uživatelům přidávat a odebírat se ze seznamu sledovaných pro určitý seznam slov v názvu a poslouchá háček pro vytváření nových stránek na wiki, aby identifikoval relevantní stránky. Když je vytvořena relevantní stránka, kód rozšíření vytvoří událost s relevantními podrobnostmi a upozorní uživatele, kteří jsou na seznamu pro tuto kombinaci slov.

Tato část ukáže, jak vytvořit tento nový typ oznámení.

Definování události

Musíme se ujistit, že naše událost je definována v systému. To se provádí pomocí extension attributes v extension.json nebo pomocí háčku BeforeCreateEchoEvent, pokud je definice události podmíněná.

Povinná je pouze vlastnost Notifications. Hodnoty NotificationCategories a NotificationIcons jsou volitelné.

Kategorie definované parametrem NotificationCategories se zobrazují v sekci Special:Preferences (což uživatelům umožňuje přizpůsobit doručování pro jednotlivé kategorie) a v dávkových e-mailech s oznámeními.

V proměnné Notifications musí být proměnná presentation-model platným názvem třídy PHP, zatímco každá položka v proměnných user-locators a user-filters musí být platnou volatelnou funkcí v PHP.

V NotificationIcons by mělo být uvedeno buď url, nebo path.

    "attributes": {
        "Echo": {
            "NotificationCategories": {
                "my-ext-topic-word-follow": {
                    "priority": 3,
                    "title": "echo-category-title-my-ext-topic-word-follow",
                    "tooltip": "echo-pref-tooltip-my-ext-topic-word-follow"
                }
            },
            "Notifications": {
                "my-ext-topic-word": {
                    "category": "my-ext-topic-word-follow",
                    "group": "positive",
                    "section": "alert",
                    "presentation-model": "MyExtTopicWordPresentationModel",
                    "bundle": {
                        "web": true,
                        "expandable": true
                    }
                }
            },
            "NotificationIcons": {
                "my-ext-topic-word": {
                    "url": "http://example.org/icon.svg"
                    "path": "MyExt/icons/TopicWordNotification.svg"
                }
            }
        }
    }

Háček přijímá parametry odpovídající těmto vlastnostem extension.json.

class MyExtHooks {
    ...

    public static function onBeforeCreateEchoEvent( &$notifications, &$notificationCategories, &$icons ) {
        if ( $this->myCondition() ) {
            $notifications["my-ext-topic-word"][...] = ...;
        }
    }

    ...
}

Další příklad najdete v [rozšíření https://gerrit.wikimedia.org/g/mediawiki/extensions/Thanks/+/93c60b6f79887f7aa5268b12c4494141cf7d95ca/includes/EchoHooks.php#25 Thanks].

Výběr příjemců oznámení

Systém oznámení se pokusí upozornit relevantní uživatele, ale nemůže uhodnout, kdo to jsou. Kód, který vytváří událost, musí poskytnout tyto informace. Existují dva způsoby, jak to udělat.

Přímé zadání

Pokud již při vytváření události víte, kdo by měl oznámení obdržet, můžete seznam příjemců předat jako hodnotu RecipientSet v druhém parametru Event::create().

Určeno lokátory a filtry

Pokud je vypsání příjemců složitější, budete muset v definici události zadat hodnotu user-locators. Můžete také zadat hodnotu user-filters, abyste z lokátorů vyloučili některé výsledky.

K tomu bychom měli vytvořit funkci, která se následně vloží do definice nové události.

    "attributes": {
        "Echo": {
            "Notifications": {
                "my-ext-topic-word": {
                    ...
                    "user-locators": [
                        "MyExtensionClass::locateUsersInList"
                    ],
                    "user-filters": [
                        "MyExtensionClass::locateMentionedUsers"
                    ]
                }
            }
        }
    }

V tomto případě budeme chtít projít seznam uživatelů a přidat je všechny:

class MyExtensionClass {
    ...

    /**
     * @param Event $event
     * @return array
     */
    public function locateUsersInList( Event $event ) {
        // Get the list of users
        $userIds = $this->getList( $event )->getUsers();
        
        return array_map( function ( $userId ) {
            return User::newFromId( $userId );
        }, $userIds );
    }

    ...
}

Příklad filtruje zmíněné uživatele v událostech komentářů v DiscussionTools.

Přidání kódu pro spuštění události

Nyní, když máme způsob, jak identifikovat, koho chceme upozornit, musíme tuto událost skutečně spustit. Naše hypotetické rozšíření naslouchá háčku vytváření stránky a poté zkontroluje, zda nový název obsahuje slova, která se hodí do seznamu Jakkoli to kód rozšíření dělá, jakmile identifikuje název, který se hodí do seznamu, vytvoří událost a upozorní příslušné uživatele.

Níže uvedený kód přeskočí skutečnou operaci poslechu vytváření nové stránky a kontroly slov a skočí přímo do definování nové události:

// ...
// Some code to listen to new page creation, check words
// and find the relevant list
// ...

// Creating the event
Event::create( [
	'type' => 'my-ext-topic-word',
	'title' => $title,
	'extra' => [
		'revid' => $revision->getId(),
		'excerpt' => DiscussionParser::getEditExcerpt( $revision, $this->getLanguage() ),
	],
	'agent' => $currentUser,
], new RecipientSet( $mentionedUser ) );

Passing the `RecipientSet` when calling `Event::create()` is recommended, although the parameter is optional, you can omit it and use an user locator instead.

Vytvoření prezentačního modelu

Nyní, když je vše nastaveno, můžeme vytvořit náš prezentační model. Budeme muset rozšířit EchoEventPresentationModel a implementovat naše metody. Můžete vidět několik příkladů těchto typů modelů v několika rozšířeních, jako je DiscussionTools [1], Thanks [2] a uvnitř samotného Echo [3][4][5]. Zde je příklad prezentačního modelu pro naše hypotetické rozšíření:

<?php
class MyExtTopicWordPresentationModel extends EchoEventPresentationModel {
	public function canRender() {
	    // Define that we have to have the page this is
	    // refering to as a condition to display this
	    // notification
		return (bool)$this->event->getTitle();
	}
	public function getIconType() {
	    // You can use existing icons in Echo icon folder
	    // or define your own through "NotificationIcons" in extension.json
		return 'someIcon';
	}
	public function getHeaderMessage() {
		if ( $this->isBundled() ) {
		    // This is the header message for the bundle that contains
		    // several notifications of this type
			$msg = $this->msg( 'notification-bundle-myext-topic-word' );
			$msg->params( $this->getBundleCount() );
			$msg->params( $this->getTruncatedTitleText( $this->event->getTitle(), true ) );
			$msg->params( $this->getViewingUserForGender() );
			return $msg;
		} else {
		    // This is the header message for individual non-bundle message
			$msg = $this->getMessageWithAgent( 'notification-myext-topic-word' );
			$msg->params( $this->getTruncatedTitleText( $this->event->getTitle(), true ) );
			$msg->params( $this->getViewingUserForGender() );
			return $msg;
		}
	}
	public function getCompactHeaderMessage() {
	    // This is the header message for individual notifications
	    // *inside* the bundle
		$msg = parent::getCompactHeaderMessage();
		$msg->params( $this->getViewingUserForGender() );
		return $msg;
	}
	public function getBodyMessage() {
	    // This is the body message.
        // We will retrieve the edit summary that we added earlier with Event::create().
		$comment = $this->event->getExtraParam( 'excerpt', false );
		if ( $comment ) {
			// Create a dummy message to contain the excerpt.
			$msg = new RawMessage( '$1' );
			$msg->plaintextParams( $comment );
			return $msg;
		}
	}
	public function getPrimaryLink() {
	    // This is the link to the new page
	    $link = $this->getPageLink( $this->event->getTitle(), '', true );
        return $link;
    }

	public function getSecondaryLinks() {
		if ( $this->isBundled() ) {
		    // For the bundle, we don't need secondary actions
			return [];
		} else {
		    // For individual items, display a link to the user
		    // that created this page
			return [ $this->getAgentLink() ];
		}
	}
}

A to je vše! Naše nové upozornění je nyní aktivní a bude se zobrazovat příslušným uživatelům s hlavičkou a tělem zpráv a primárními a sekundárními odkazy, které jsme definovali.

Jak seskupovat oznámení

  • Ujistěte se, že je konfigurace balíčku nastavena na hodnotu true v definici události echo:
    "Notifications": {
        "my-ext-topic-word": {
            ...
            "bundle": {
                "web": true,
                "email": true,
                "expandable": true
            },
            ...
        }
    },
  • Přidejte háček pro pravidla sdružování:
public static function onEchoGetBundleRules( $event, &$bundleString ) {
    switch ( $event->getType() ) {
        // Your notification type. This is the key that you set in "Notifications" in extension.json.
        case 'my-ext-topic-word':
            // Which messages go into which bundle. For the same bundle, return the same $bundleString.
            $bundleString = 'my-ext-topic';
        break;
    }
    return true;
}
  • Připojte funkci v extension.json:
    "Hooks": {
        "EchoGetBundleRules": [
            "LoginNotifyHooks::onEchoGetBundleRules"
        ]
        // More stuff here
    }
  • Můžete použít $this->isBundled() v kódu svého prezentačního modelu ke kontrole, zda jste v balíčku oznámení, a podle toho upravit záhlaví a tělo zprávy:
public function getHeaderMessage() {
    if ( $this->isBundled() ) {
        $msg = $this->msg( 'something' );
        return $msg;
    } else {
        ...
    }
}
  • Když se rozbalitelný balíček rozbalí, každé dílčí oznámení se vykreslí samostatně. Ve výchozím nastavení je zobrazeno getHeaderMessage(), ale doporučuje se, abyste pro tento případ uvedli kratší zprávu do kódu prezentačního modelu pomocí getCompactHeaderMessage():
public function getCompactHeaderMessage() {
    return $this->msg( 'somethingshort' );
}

A máte hotovo! Nezapomeňte přidat své zprávy. Všimněte si také, že balíčky mohou nebo nemusí být rozšiřitelné.

Úskalí a varování

  1. Role user-locators a user-filters je důležitá, abychom zajistili, že nebudeme uživatelům z jediné události poskytovat dvojité oznámení. Například, pokud je ve Flow vytvořeno nové téma, chceme upozornit všechny, kteří sledují nástěnku – ale chceme ignorovat uživatele, kteří byli zmíněni, protože 'mention' vytvoří vlastní událost.
    Při vytváření nových událostí se snažte ujistit se, že jste odfiltrovali potenciální kolidující události pro uživatele, kteří byli upozorněni, jinak dostanou dvě oznámení pro stejnou událost.
  2. Všimněte si, že canRender je neuvěřitelně důležitá metoda. Kromě ovlivnění toho, zda se oznámení zobrazí, je to také součást testu, který systém oznámení používá k moderování oznámení. Pokud bylo například vytvořeno oznámení o nové zmínce pro uživatele, ale revize, která to vyvolala, byla poté odstraněna nebo potlačena, oznámení by mělo také zmizet. Systém zkontroluje, zda lze oznámení zobrazit (mimo jiné pomocí canRender) - pokud canRender v tomto případě zkontroluje, že revize existuje, vrátí false (protože revize byla smazána), což zajistí, že oznámení bude označeno příznakem pro smazání. Nesprávné definování canRender může vést k chybám, protože oznámení se pokusí vyžádat podrobnosti o chybějícím názvu nebo revizi, takže pokud je to potřeba, dejte pozor, abyste je definovali.
  3. getSecondaryLinks musí vrátit pole. Pokud vaše oznámení obsahuje pouze sekundární odkazy v určitých případech, ujistěte se, že všechny ostatní případy vrací pole a ne false nebo null. Na rozdíl od getPrimaryLink, která vrací false pro prázdnou hodnotu, getSecondaryLinks musí vrátit pole.