Soeren.oldag has uploaded a new change for review. https://gerrit.wikimedia.org/r/205227
Change subject: UpdateTable script now takes a single tar file as source file. ...................................................................... UpdateTable script now takes a single tar file as source file. Change-Id: I98a30e067efa1bca77259fa8def135eaaf75263d --- M .gitignore M includes/UpdateTable/ImportContext.php M includes/UpdateTable/Importer.php M maintenance/UpdateTable.php M tests/phpunit/UpdateTable/ImportContextTest.php M tests/phpunit/UpdateTable/UpdateTableTest.php D tests/phpunit/UpdateTable/testdata/entities.csv A tests/phpunit/UpdateTable/testdata/external_data.tar D tests/phpunit/UpdateTable/testdata/identifiers.csv D tests/phpunit/UpdateTable/testdata/meta.csv 10 files changed, 287 insertions(+), 323 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikidataQualityExternalValidation refs/changes/27/205227/1 diff --git a/.gitignore b/.gitignore index 1cfd88c..8efd9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ *.iso *.jar *.rar -*.tar *.zip # OS generated files # diff --git a/includes/UpdateTable/ImportContext.php b/includes/UpdateTable/ImportContext.php index a435ecc..8d995ce 100644 --- a/includes/UpdateTable/ImportContext.php +++ b/includes/UpdateTable/ImportContext.php @@ -18,25 +18,11 @@ class ImportContext { /** - * Path of the entities file to be imported + * Path of the TAR file to be imported. * * @var string */ - private $entitiesFilePath; - - /** - * Path of the identifier properties file to be imported - * - * @var string - */ - private $identifierPropertiesFilePath; - - /** - * Path of the meta information file to be imported - * - * @var string - */ - private $metaInformationFilePath; + private $tarFilePath; /** * @var LoadBalancer @@ -57,17 +43,13 @@ * @param LoadBalancer $loadBalancer * @param int $batchSize * @param bool $quiet - * @param string $entitiesFilePath - * @param string $identifierPropertiesFilePath - * @param string $metaInformationFilePath + * @param string $tarFilePath */ - function __construct( LoadBalancer $loadBalancer, $batchSize, $quiet, $entitiesFilePath, $identifierPropertiesFilePath, $metaInformationFilePath ) { + function __construct( LoadBalancer $loadBalancer, $batchSize, $quiet, $tarFilePath ) { $this->setLoadBalancer( $loadBalancer ); $this->setBatchSize( $batchSize ); $this->setQuiet( $quiet ); - $this->setEntitiesFilePath( $entitiesFilePath ); - $this->setIdentifierPropertiesFilePath( $identifierPropertiesFilePath ); - $this->setMetaInformationFilePath( $metaInformationFilePath ); + $this->setTarFilePath( $tarFilePath ); } /** @@ -87,22 +69,8 @@ /** * @return string */ - public function getEntitiesFilePath() { - return $this->entitiesFilePath; - } - - /** - * @return string - */ - public function getIdentifierPropertiesFilePath() { - return $this->identifierPropertiesFilePath; - } - - /** - * @return string - */ - public function getMetaInformationFilePath() { - return $this->metaInformationFilePath; + public function getTarFilePath() { + return $this->tarFilePath; } /** @@ -142,35 +110,13 @@ } /** - * @param string $entitiesFilePath + * @param string $tarFilePath */ - public function setEntitiesFilePath( $entitiesFilePath ) { - if ( is_string( $entitiesFilePath ) ) { - $this->entitiesFilePath = $entitiesFilePath; + public function setTarFilePath( $tarFilePath ) { + if ( is_string( $tarFilePath ) ) { + $this->tarFilePath = $tarFilePath; } else { throw new InvalidArgumentException( '$entitiesFilePath must be of type string.' ); - } - } - - /** - * @param string $identifierPropertiesFilePath - */ - public function setIdentifierPropertiesFilePath( $identifierPropertiesFilePath ) { - if ( is_string( $identifierPropertiesFilePath ) ) { - $this->identifierPropertiesFilePath = $identifierPropertiesFilePath; - } else { - throw new InvalidArgumentException( '$identifierPropertiesFilePath must be of type string.' ); - } - } - - /** - * @param string $metaInformationFilePath - */ - public function setMetaInformationFilePath( $metaInformationFilePath ) { - if ( is_string( $metaInformationFilePath ) ) { - $this->metaInformationFilePath = $metaInformationFilePath; - } else { - throw new InvalidArgumentException( '$metaInformationFilePath must be of type string.' ); } } } \ No newline at end of file diff --git a/includes/UpdateTable/Importer.php b/includes/UpdateTable/Importer.php index 77cefc6..1ac95c6 100644 --- a/includes/UpdateTable/Importer.php +++ b/includes/UpdateTable/Importer.php @@ -2,13 +2,14 @@ namespace WikidataQuality\ExternalValidation\UpdateTable; +use DatabaseBase; use DateTime; use DateTimeZone; use Doctrine\Instantiator\Exception\InvalidArgumentException; +use MWException; +use PharData; use Wikibase\DataModel\Entity\ItemId; use WikidataQuality\ExternalValidation\DumpMetaInformation; -use DatabaseBase; -use MWException; /** @@ -20,240 +21,292 @@ */ class Importer { - /** - * @var ImportContext - */ - private $importContext; + const EXTERNAL_DATA_FILE_NAME = "external_data.csv"; + const DUMP_INFORMATION_FILE_NAME = "dump_information.csv"; + const IDENTIFIER_PROPERTIES_FILE_NAME = "identifier_properties.csv"; - /** - * @param ImportContext $importContext - */ - public function __construct( ImportContext $importContext ) { - $this->importContext = $importContext; - } + /** + * @var ImportContext + */ + private $importContext; - /** - * Starts the whole import process - */ - function import() { - $db = $this->establishDbConnection(); + /** + * @var string + */ + private $entitiesFilePath; - if ( !$this->assertTablesExist( $db ) ) { - return; - } + /** + * @var string + */ + private $dumpInformationFilePath; - $dumpIds = $this->insertMetaInformation( $db ); + /** + * @var string + */ + private $identifierPropertiesFilePath; - $this->deleteOldDatabaseEntries( $db, $dumpIds, DUMP_DATA_TABLE ); + /** + * @param ImportContext $importContext + */ + public function __construct( ImportContext $importContext ) { + $this->importContext = $importContext; + } - $this->deleteOldDatabaseEntries( $db, $dumpIds, DUMP_IDENTIFIER_PROPERTIES_TABLE ); + /** + * Starts the whole import process + */ + function import() { + $this->extractTarFile(); - $this->insertIdentifierProperties( $db ); + $db = $this->establishDbConnection(); - $this->insertExternalValues( $db ); + if ( !$this->assertTablesExist( $db ) ) { + return; + } - $this->reuseDbConnection( $db ); - } + $dumpIds = $this->insertMetaInformation( $db ); - /** - * Checks, if needed database tables exist - * - * @param DatabaseBase $db - * - * @return bool - */ - private function assertTablesExist( DatabaseBase $db ) { - $success = true; - $tablesUsed = array ( DUMP_META_TABLE, DUMP_IDENTIFIER_PROPERTIES_TABLE, DUMP_DATA_TABLE ); - foreach ( $tablesUsed as $tableName ) { - if ( !$db->tableExists( $tableName ) ) { - $success = false; - if ( !$this->importContext->isQuiet() ) { - print "$tableName table does not exist.\nExecuting core/maintenance/update.php may help.\n"; - } - } - } + $this->deleteOldDatabaseEntries( $db, $dumpIds, DUMP_DATA_TABLE ); - return $success; - } + $this->deleteOldDatabaseEntries( $db, $dumpIds, DUMP_IDENTIFIER_PROPERTIES_TABLE ); - /** - * Delete old database entries with given dump ids - * - * @param DatabaseBase $db - * @param array $dumpIds - * @param string $tableName - */ - protected function deleteOldDatabaseEntries( DatabaseBase $db, array $dumpIds, $tableName ) { - if ( !is_string( $tableName ) ) { - throw new InvalidArgumentException( '$tableName must be string.' ); - } + $this->insertIdentifierProperties( $db ); - global $wgDBtype; + $this->insertExternalValues( $db ); - if ( !$this->importContext->isQuiet() ) { - print "Removing old entries\n"; - print "\n"; - } + $this->deleteExtractedFiles(); - foreach ( $dumpIds as $dumpId ) { - if ( $wgDBtype === 'sqlite' ) { - $db->delete( $tableName, "dump_id='$dumpId'" ); - } else { - $i = 0; - do { - $db->commit( __METHOD__, 'flush' ); - wfWaitForSlaves(); - $table = $db->tableName( $tableName ); - $batchSize = $this->importContext->getBatchSize(); - $db->query( "DELETE FROM $table WHERE dump_id='$dumpId' LIMIT $batchSize" ); + $this->reuseDbConnection( $db ); + } - $i++; - if ( !$this->importContext->isQuiet() ) { - print "\r\033[K"; - print "$i batches deleted"; - } - } while ( $db->affectedRows() > 0 ); + /** + * Extracts the TAR archive to be imported + */ + private function extractTarFile() { + $test = new PharData( $this->importContext->getTarFilePath() ); + $tempDir = sys_get_temp_dir(); + $test->extractTo( $tempDir, null, true ); - if ( !$this->importContext->isQuiet() ) { - print "\n"; - } - } - } - } + $this->entitiesFilePath = $tempDir . DIRECTORY_SEPARATOR . self::EXTERNAL_DATA_FILE_NAME; + $this->dumpInformationFilePath = $tempDir . DIRECTORY_SEPARATOR . self::DUMP_INFORMATION_FILE_NAME; + $this->identifierPropertiesFilePath = $tempDir . DIRECTORY_SEPARATOR . self::IDENTIFIER_PROPERTIES_FILE_NAME; + } - /** - * Establishes a database connection using the load balancer - * - * @return DatabaseBase - * @throws MWException - */ - protected function establishDbConnection() { - $loadBalancer = $this->importContext->getLoadBalancer(); - $db = $loadBalancer->getConnection( DB_MASTER ); + /** + * Deletes the extracted CSV files + */ + private function deleteExtractedFiles() { + if ( file_exists( $this->entitiesFilePath ) ) { + unlink( $this->entitiesFilePath ); + } + $this->entitiesFilePath = null; - return $db; - } + if ( file_exists( $this->dumpInformationFilePath ) ) { + unlink( $this->dumpInformationFilePath ); + } + $this->dumpInformationFilePath = null; - /** - * Mark database connection as being available for reuse - * - * @param DatabaseBase $db - */ - protected function reuseDbConnection( DatabaseBase $db ) { - $loadBalancer = $this->importContext->getLoadBalancer(); - $loadBalancer->reuseConnection( $db ); - } + if ( file_exists( $this->identifierPropertiesFilePath ) ) { + unlink( $this->identifierPropertiesFilePath ); + } + $this->identifierPropertiesFilePath = null; + } - /** - * Inserts meta information stored in csv file into database - * - * @param DatabaseBase $db - * - * @return array - */ - protected function insertMetaInformation( DatabaseBase $db ) { - $csvFile = fopen( $this->importContext->getMetaInformationFilePath(), 'rb' ); + /** + * Checks, if needed database tables exist + * + * @param DatabaseBase $db + * + * @return bool + */ + private function assertTablesExist( DatabaseBase $db ) { + $tablesUsed = array( DUMP_META_TABLE, DUMP_IDENTIFIER_PROPERTIES_TABLE, DUMP_DATA_TABLE ); + foreach ( $tablesUsed as $tableName ) { + if ( !$db->tableExists( $tableName ) ) { + if ( !$this->importContext->isQuiet() ) { + print "$tableName table does not exist.\nExecuting core/maintenance/update.php may help.\n"; + } - $dumpIds = array (); - while ( $data = fgetcsv( $csvFile ) ) { - $metaInformation = new DumpMetaInformation( - $data[ 0 ], - new ItemId( $data[ 1 ] ), - new DateTime( $data[ 2 ], new DateTimeZone( 'UTC' ) ), - $data[ 3 ], - $data[ 4 ], - intval( $data[ 5 ] ), - new ItemId( $data[ 6 ] ) - ); - $metaInformation->save( $db ); + return false; + } + } - $dumpIds[ ] = $metaInformation->getDumpId(); - } + return true; + } - fclose( $csvFile ); + /** + * Establishes a database connection using the load balancer + * + * @return DatabaseBase + * @throws MWException + */ + protected function establishDbConnection() { + $loadBalancer = $this->importContext->getLoadBalancer(); + $db = $loadBalancer->getConnection( DB_MASTER ); - return $dumpIds; - } + return $db; + } - /** - * Inserts identifier properties stored in csv file into database - * - * @param DatabaseBase $db - */ - protected function insertIdentifierProperties( DatabaseBase $db ) { - $csvFile = fopen( $this->importContext->getIdentifierPropertiesFilePath(), 'rb' ); + /** + * Mark database connection as being available for reuse + * + * @param DatabaseBase $db + */ + protected function reuseDbConnection( DatabaseBase $db ) { + $loadBalancer = $this->importContext->getLoadBalancer(); + $loadBalancer->reuseConnection( $db ); + } - while ( $data = fgetcsv( $csvFile ) ) { - $accumulator = array ( - 'identifier_pid' => $data[ 0 ], - 'dump_id' => $data[ 1 ] - ); + /** + * Delete old database entries with given dump ids + * + * @param DatabaseBase $db + * @param array $dumpIds + * @param string $tableName + */ + protected function deleteOldDatabaseEntries( DatabaseBase $db, array $dumpIds, $tableName ) { + global $wgDBtype; - $existing = $db->selectRow( - DUMP_IDENTIFIER_PROPERTIES_TABLE, - array ( - 'identifier_pid', - 'dump_id' - ), - $accumulator - ); + if ( !$this->importContext->isQuiet() ) { + print "Removing old entries\n"; + print "\n"; + } - if ( !$existing ) { - $db->insert( - DUMP_IDENTIFIER_PROPERTIES_TABLE, - $accumulator - ); - } - } + foreach ( $dumpIds as $dumpId ) { + if ( $wgDBtype === 'sqlite' ) { + $db->delete( $tableName, "dump_id='$dumpId'" ); + } else { + $i = 0; + do { + $db->commit( __METHOD__, 'flush' ); + wfWaitForSlaves(); + $table = $db->tableName( $tableName ); + $batchSize = $this->importContext->getBatchSize(); + $db->query( "DELETE FROM $table WHERE dump_id='$dumpId' LIMIT $batchSize" ); - fclose( $csvFile ); - } + $i++; + if ( !$this->importContext->isQuiet() ) { + print "\r\033[K"; + print "$i batches deleted"; + } + } while ( $db->affectedRows() > 0 ); - /** - * Inserts external values stored in csv file into database - * - * @param DatabaseBase $db - */ - protected function insertExternalValues( DatabaseBase $db ) { - if ( !$this->importContext->isQuiet() ) { - print "Insert new entries\n"; - } + if ( !$this->importContext->isQuiet() ) { + print "\n"; + } + } + } + } - $csvFile = fopen( $this->importContext->getEntitiesFilePath(), 'rb' ); + /** + * Inserts meta information stored in csv file into database + * + * @param DatabaseBase $db + * + * @return array + */ + protected function insertMetaInformation( DatabaseBase $db ) { + $csvFile = fopen( $this->dumpInformationFilePath, 'rb' ); - $i = 0; - $accumulator = array (); - while ( true ) { - $data = fgetcsv( $csvFile ); - if ( $data === false || ++$i % $this->importContext->getBatchSize() === 0 ) { - $db->commit( __METHOD__, 'flush' ); - wfWaitForSlaves(); - $db->insert( DUMP_DATA_TABLE, $accumulator ); - if ( !$this->importContext->isQuiet() ) { - print "\r\033[K"; - print "$i rows inserted"; - } + $dumpIds = array(); + while ( $data = fgetcsv( $csvFile ) ) { + $metaInformation = new DumpMetaInformation( + $data[ 0 ], + new ItemId( $data[ 1 ] ), + new DateTime( $data[ 2 ], new DateTimeZone( 'UTC' ) ), + $data[ 3 ], + $data[ 4 ], + intval( $data[ 5 ] ), + new ItemId( $data[ 6 ] ) + ); + $metaInformation->save( $db ); - $accumulator = array (); + $dumpIds[ ] = $metaInformation->getDumpId(); + } - if ( $data === false ) { - break; - } - } + fclose( $csvFile ); - $accumulator[ ] = array ( - 'dump_id' => $data[ 0 ], - 'external_id' => $data[ 1 ], - 'pid' => $data[ 2 ], - 'external_value' => $data[ 3 ], - ); - } + return $dumpIds; + } - if ( !$this->importContext->isQuiet() ) { - print "\n"; - } + /** + * Inserts identifier properties stored in csv file into database + * + * @param DatabaseBase $db + */ + protected function insertIdentifierProperties( DatabaseBase $db ) { + $csvFile = fopen( $this->identifierPropertiesFilePath, 'rb' ); - fclose( $csvFile ); - } + while ( $data = fgetcsv( $csvFile ) ) { + $accumulator = array( + 'identifier_pid' => $data[ 0 ], + 'dump_id' => $data[ 1 ] + ); + + $existing = $db->selectRow( + DUMP_IDENTIFIER_PROPERTIES_TABLE, + array( + 'identifier_pid', + 'dump_id' + ), + $accumulator + ); + + if ( !$existing ) { + $db->insert( + DUMP_IDENTIFIER_PROPERTIES_TABLE, + $accumulator + ); + } + } + + fclose( $csvFile ); + } + + /** + * Inserts external values stored in csv file into database + * + * @param DatabaseBase $db + */ + protected function insertExternalValues( DatabaseBase $db ) { + if ( !$this->importContext->isQuiet() ) { + print "Insert new entries\n"; + } + + $csvFile = fopen( $this->entitiesFilePath, 'rb' ); + + $i = 0; + $accumulator = array(); + while ( true ) { + $data = fgetcsv( $csvFile ); + if ( $data === false || ++$i % $this->importContext->getBatchSize() === 0 ) { + $db->commit( __METHOD__, 'flush' ); + wfWaitForSlaves(); + $db->insert( DUMP_DATA_TABLE, $accumulator ); + if ( !$this->importContext->isQuiet() ) { + print "\r\033[K"; + print "$i rows inserted"; + } + + $accumulator = array(); + + if ( $data === false ) { + break; + } + } + + $accumulator[ ] = array( + 'dump_id' => $data[ 0 ], + 'external_id' => $data[ 1 ], + 'pid' => $data[ 2 ], + 'external_value' => $data[ 3 ], + ); + } + + if ( !$this->importContext->isQuiet() ) { + print "\n"; + } + + fclose( $csvFile ); + } } \ No newline at end of file diff --git a/maintenance/UpdateTable.php b/maintenance/UpdateTable.php index ad984b1..81e3c2d 100644 --- a/maintenance/UpdateTable.php +++ b/maintenance/UpdateTable.php @@ -23,30 +23,26 @@ */ class UpdateTable extends Maintenance { - function __construct() { - parent::__construct(); - $this->mDescription = "Downloads dumps of external databases and imports the entities into the local database."; - $this->addOption( 'entities-file', 'CSV file that contains external entities.', true, true ); - $this->addOption( 'identifier-properties-file', 'CSV file that contains identifier properties and the related dump ids..', true, true ); - $this->addOption( 'meta-information-file', 'CSV file that contains meta information about the data source.', true, true ); - $this->setBatchSize( 1000 ); - } + function __construct() { + parent::__construct(); + $this->mDescription = "Downloads dumps of external databases and imports the entities into the local database."; + $this->addOption( 'tar-file', 'TAR file that contains external data for import.', true, true ); + $this->setBatchSize( 1000 ); + } - function execute() { - wfWaitForSlaves(); - $loadBalancer = wfGetLB(); + function execute() { + wfWaitForSlaves(); + $loadBalancer = wfGetLB(); - $context = new ImportContext( - $loadBalancer, - $this->mBatchSize, - $this->isQuiet(), - $this->getOption( 'entities-file' ), - $this->getOption( 'identifier-properties-file' ), - $this->getOption( 'meta-information-file' ) - ); - $importer = new Importer( $context ); - $importer->import(); - } + $context = new ImportContext( + $loadBalancer, + $this->mBatchSize, + $this->isQuiet(), + $this->getOption( 'tar-file' ) + ); + $importer = new Importer( $context ); + $importer->import(); + } } // @codeCoverageIgnoreStart diff --git a/tests/phpunit/UpdateTable/ImportContextTest.php b/tests/phpunit/UpdateTable/ImportContextTest.php index 9f2558d..28d958c 100644 --- a/tests/phpunit/UpdateTable/ImportContextTest.php +++ b/tests/phpunit/UpdateTable/ImportContextTest.php @@ -18,9 +18,9 @@ * * @dataProvider provideInvalidArguments() */ - public function testConstructWithInvalidArguments( $loadBalancer, $batchSize, $quiet, $entitiesFilePath, $identifierPropertiesFilePath, $metaInformationFilePath ) { + public function testConstructWithInvalidArguments( $loadBalancer, $batchSize, $quiet, $tarFilePath ) { $this->setExpectedException( 'InvalidArgumentException' ); - new ImportContext( $loadBalancer, $batchSize, $quiet, $entitiesFilePath, $identifierPropertiesFilePath, $metaInformationFilePath ); + new ImportContext( $loadBalancer, $batchSize, $quiet, $tarFilePath ); } public function provideInvalidArguments() { @@ -32,40 +32,18 @@ $loadBalancer, 'invalidBatchSize', true, - 'entitiesFilePath', - 'identifierPropertiesFilePath', - 'metaInformationFilePath' + 'entitiesFilePath' ), array( $loadBalancer, 1234, 'invalidQuiet', - 'entitiesFilePath', - 'identifierPropertiesFilePath', - 'metaInformationFilePath' + 'entitiesFilePath' ), array( $loadBalancer, 1234, true, - 1234, - 'identifierPropertiesFilePath', - 'metaInformationFilePath' - ), - array( - $loadBalancer, - 1234, - true, - 'entitiesFilePath', - 1234, - 'metaInformationFilePath' - ), - array( - $loadBalancer, - 1234, - true, - 'entitiesFilePath', - 'identifierPropertiesFilePath', 1234 ) ); diff --git a/tests/phpunit/UpdateTable/UpdateTableTest.php b/tests/phpunit/UpdateTable/UpdateTableTest.php index 164220f..e06bf84 100644 --- a/tests/phpunit/UpdateTable/UpdateTableTest.php +++ b/tests/phpunit/UpdateTable/UpdateTableTest.php @@ -93,9 +93,7 @@ // Execute script $maintenanceScript = new UpdateTable(); $args = array( - 'entities-file' => __DIR__ . '/testdata/entities.csv', - 'identifier-properties-file' => __DIR__ . '/testdata/identifiers.csv', - 'meta-information-file' => __DIR__ . '/testdata/meta.csv', + 'tar-file' => __DIR__ . '/testdata/external_data.tar', 'batch-size' => 2, 'quiet' => true ); diff --git a/tests/phpunit/UpdateTable/testdata/entities.csv b/tests/phpunit/UpdateTable/testdata/entities.csv deleted file mode 100644 index 4e42258..0000000 --- a/tests/phpunit/UpdateTable/testdata/entities.csv +++ /dev/null @@ -1,3 +0,0 @@ -foobar,100001718,19,Parma -foobar,100001718,20,Paris -fubar,100001718,569,01.06.1771 \ No newline at end of file diff --git a/tests/phpunit/UpdateTable/testdata/external_data.tar b/tests/phpunit/UpdateTable/testdata/external_data.tar new file mode 100644 index 0000000..67bdd65 --- /dev/null +++ b/tests/phpunit/UpdateTable/testdata/external_data.tar Binary files differ diff --git a/tests/phpunit/UpdateTable/testdata/identifiers.csv b/tests/phpunit/UpdateTable/testdata/identifiers.csv deleted file mode 100644 index e0eb4c8..0000000 --- a/tests/phpunit/UpdateTable/testdata/identifiers.csv +++ /dev/null @@ -1,2 +0,0 @@ -P227,foobar -P227,fubar \ No newline at end of file diff --git a/tests/phpunit/UpdateTable/testdata/meta.csv b/tests/phpunit/UpdateTable/testdata/meta.csv deleted file mode 100644 index 5c0379e..0000000 --- a/tests/phpunit/UpdateTable/testdata/meta.csv +++ /dev/null @@ -1 +0,0 @@ -foobar,Q36578,20150401144144,de,"http://www.foo.bar",590798465,Q6938433 \ No newline at end of file -- To view, visit https://gerrit.wikimedia.org/r/205227 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I98a30e067efa1bca77259fa8def135eaaf75263d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/WikidataQualityExternalValidation Gerrit-Branch: master Gerrit-Owner: Soeren.oldag <soeren_ol...@freenet.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits