jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/341521 )
Change subject: WikibaseQualityConstraints Extension (API)
......................................................................
WikibaseQualityConstraints Extension (API)
Change-Id: Icf4ec91bc0a06435bcf626e2060008d0d88027a8
---
M WikibaseQualityConstraints.php
A api/CheckConstraints.php
M composer.json
M i18n/en.json
M i18n/qqq.json
M includes/ConstraintCheck/DelegatingConstraintChecker.php
A includes/ConstraintParameterRenderer.php
M includes/ConstraintReportFactory.php
M specials/SpecialConstraintReport.php
A tests/phpunit/Api/CheckConstraintsTest.php
M tests/phpunit/DelegatingConstraintCheckerTest.php
A tests/phpunit/Q6.json
12 files changed, 1,031 insertions(+), 90 deletions(-)
Approvals:
Jonas Kress (WMDE): Looks good to me, approved
jenkins-bot: Verified
diff --git a/WikibaseQualityConstraints.php b/WikibaseQualityConstraints.php
index bc994f9..70460fa 100644
--- a/WikibaseQualityConstraints.php
+++ b/WikibaseQualityConstraints.php
@@ -29,6 +29,16 @@
// Initialize special pages
$GLOBALS['wgSpecialPages']['ConstraintReport'] =
'WikibaseQuality\ConstraintReport\Specials\SpecialConstraintReport::newFromGlobalState';
+ $GLOBALS['wgAPIModules']['wbcheckconstraints'] = [
+ 'class' =>
'\WikibaseQuality\ConstraintReport\Api\CheckConstraints',
+ 'factory' => function ( $main, $name ) {
+ return
\WikibaseQuality\ConstraintReport\Api\CheckConstraints::newFromGlobalState(
+ $main,
+ $name
+ );
+ }
+ ];
+
// Define modules
$remoteExtPathParts = explode(
DIRECTORY_SEPARATOR . 'extensions' . DIRECTORY_SEPARATOR,
__DIR__, 2
diff --git a/api/CheckConstraints.php b/api/CheckConstraints.php
new file mode 100644
index 0000000..00a5f8f
--- /dev/null
+++ b/api/CheckConstraints.php
@@ -0,0 +1,378 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Api;
+
+use ApiBase;
+use ApiMain;
+use ApiResult;
+use DataValues\DataValue;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
+
+use RequestContext;
+
+use Wikibase\DataModel\Entity\EntityId;
+
+use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Entity\EntityIdParsingException;
+use Wikibase\DataModel\Services\Statement\StatementGuidValidator;
+use Wikibase\DataModel\Services\Statement\StatementGuidParser;
+use Wikibase\Repo\Api\ApiErrorReporter;
+use Wikibase\Repo\Api\ApiHelperFactory;
+
+use Wikibase\Repo\WikibaseRepo;
+
+use WikibaseQuality\ConstraintReport\ConstraintReportFactory;
+use
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
+
+use Wikibase\Repo\EntityIdLabelFormatterFactory;
+use ValueFormatters\FormatterOptions;
+use Wikibase\Lib\OutputFormatValueFormatterFactory;
+use Wikibase\Lib\SnakFormatter;
+
+use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
+use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
+
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\PropertyId;
+
+use Wikibase\ChangeOp\StatementChangeOpFactory;
+use Wikibase\LanguageFallbackChain;
+use Wikibase\LanguageFallbackChainFactory;
+use Wikibase\DataModel\Services\Lookup\TermLookup;
+use Wikibase\DataModel\Services\Term\TermBuffer;
+use Wikimedia\Assert\Assert;
+use Language;
+
+/**
+ * API module that performs constraint check of entities, claims and
constraint ID
+ *
+ * @author Olga Bode
+ * @license GNU GPL v2+
+ */
+class CheckConstraints extends ApiBase {
+
+ const PARAM_ID = 'id';
+ const PARAM_CLAIM_ID = 'claimid';
+ const PARAM_CONSTRAINT_ID = 'constraintid';
+
+ /**
+ *
+ * @var EntityIdParser
+ */
+ private $entityIdParser;
+
+ /**
+ *
+ * @var StatementGuidValidator
+ */
+ private $statementGuidValidator;
+
+ /**
+ *
+ * @var StatementGuidParser
+ */
+ private $statementGuidParser;
+
+ /**
+ *
+ * @var DelegatingConstraintChecker
+ */
+ private $delegatingConstraintChecker;
+
+ /**
+ *
+ * @var ApiErrorReporter
+ */
+ private $errorReporter;
+
+ /**
+ *
+ * @var EntityIdFormatter
+ */
+ private $entityIdLabelFormatter;
+
+ /**
+ *
+ * @var StatementChangeOpFactory
+ */
+ private $statementChangeOpFactory;
+
+ /**
+ *
+ * @var ConstraintParameterRenderer
+ */
+ private $constraintParameterRenderer;
+
+ /**
+ * @var TermLookup
+ */
+ private $termLookup;
+
+ /**
+ * Creates new instance from global state.
+ *
+ * @param ApiMain $main
+ * @param string $name
+ * @param string $prefix
+ *
+ * @return self
+ */
+ public static function newFromGlobalState( ApiMain $main, $name,
$prefix = '' ) {
+ $repo = WikibaseRepo::getDefaultInstance();
+ $constraintReportFactory =
ConstraintReportFactory::getDefaultInstance();
+ $changeOpFactoryProvider = $repo->getChangeOpFactoryProvider();
+ $termLookup = $repo->getTermLookup();
+ $termBuffer = $repo->getTermBuffer();
+ $languageFallbackChainFactory = new
LanguageFallbackChainFactory();
+ $fallbackLabelDescLookupFactory = new
LanguageFallbackLabelDescriptionLookupFactory( $languageFallbackChainFactory,
$termLookup, $termBuffer );
+ $factory = new EntityIdLabelFormatterFactory();
+
+ $language = new Language();
+ $labelLookup =
$fallbackLabelDescLookupFactory->newLabelDescriptionLookup( $language );
+
+ $formatterOptions = new FormatterOptions();
+ $factoryFunctions = [];
+ Assert::parameterElementType( 'callable', $factoryFunctions,
'$factoryFunctions' );
+ $formatterOptions->setOption( SnakFormatter::OPT_LANG,
$language->getCode() );
+ $valueFormatterFactory = new OutputFormatValueFormatterFactory(
$factoryFunctions, $language,$languageFallbackChainFactory );
+ $valueFormatter = $valueFormatterFactory->getValueFormatter(
SnakFormatter::FORMAT_HTML, $formatterOptions );
+
+ return new CheckConstraints( $main, $name, $prefix,
$repo->getEntityIdParser(),
+ $repo->getStatementGuidValidator(),
$repo->getStatementGuidParser(),
$constraintReportFactory->getConstraintChecker(),
+ new ConstraintParameterRenderer(
$factory->getEntityIdFormatter( $labelLookup ), $valueFormatter ),
+ $repo->getApiHelperFactory( RequestContext::getMain() )
);
+ }
+
+ /**
+ *
+ * @param ApiMain $main
+ * @param string $name
+ * @param string $prefix
+ * @param EntityIdParser $entityIdParser
+ * @param StatementGuidValidator $statementGuidValidator
+ * @param StatementGuidParser $statementGuidParser
+ * @param DelegatingConstraintChecker $delegatingConstraintChecker
+ * @param ConstraintParameterRenderer $constraintParameterRenderer
+ * @param ApiHelperFactory $apiHelperFactory
+ */
+ public function __construct( ApiMain $main, $name, $prefix = '',
EntityIdParser $entityIdParser,
+ StatementGuidValidator $statementGuidValidator,
+ StatementGuidParser $statementGuidParser,
+ DelegatingConstraintChecker $delegatingConstraintChecker,
+ ConstraintParameterRenderer $constraintParameterRenderer,
+ ApiHelperFactory $apiHelperFactory ) {
+ parent::__construct( $main, $name, $prefix );
+
+ $repo = WikibaseRepo::getDefaultInstance();
+ $this->statementDeserializer =
$repo->getExternalFormatStatementDeserializer();
+ $changeOpFactoryProvider = $repo->getChangeOpFactoryProvider();
+
+ $this->statementChangeOpFactory =
$changeOpFactoryProvider->getStatementChangeOpFactory();
+
+ $this->entityIdParser = $entityIdParser;
+ $this->statementGuidValidator = $statementGuidValidator;
+ $this->statementGuidParser = $statementGuidParser;
+ $this->delegatingConstraintChecker =
$delegatingConstraintChecker;
+ $this->resultBuilder = $apiHelperFactory->getResultBuilder(
$this );
+ $this->errorReporter = $apiHelperFactory->getErrorReporter(
$this );
+
+ $this->constraintParameterRenderer =
$constraintParameterRenderer;
+ }
+
+ /**
+ * Evaluates the parameters, runs the requested constraint check, and
sets up the result
+ */
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $output = [];
+
+ $this->validateParameters( $params );
+ $entityIds = $this->parseEntityIds( $params );
+ $claimIds = $this->parseClaimIds( $params );
+
+ $output = array_merge( $output, $this->checkItems( $entityIds,
$params[self::PARAM_CONSTRAINT_ID] ) );
+ $output = array_merge( $output, $this->checkClaimIds(
$claimIds, $params[self::PARAM_CONSTRAINT_ID] ) );
+
+ $this->getResult()->addValue( null, $this->getModuleName(),
$this->buildResult( $output, $params[self::PARAM_ID] ) );
+ $this->resultBuilder->markSuccess( 1 );
+ }
+
+
+ private function checkItems( array $entityIds, $constraintIds ) {
+
+ $checkResults = [];
+ foreach ( $entityIds as $entityId ) {
+ $currentCheckResults =
$this->delegatingConstraintChecker->checkAgainstConstraintsOnEntityId(
+ $entityId, $constraintIds );
+ if ( $currentCheckResults ) {
+ $checkResults = array_merge( $checkResults,
$currentCheckResults );
+ }
+ }
+
+ return $checkResults;
+ }
+
+ private function checkClaimIds( array $claimIds, $constraintIds ) {
+
+ $checkResults = [];
+ foreach ( $claimIds as $claimId ) {
+ $currentCheckResults =
$this->delegatingConstraintChecker->checkAgainstConstraintsOnClaimId(
+ $claimId, $constraintIds );
+ if ( $currentCheckResults ) {
+ $checkResults = array_merge( $checkResults,
$currentCheckResults );
+ }
+ }
+
+ return $checkResults;
+ }
+
+ private function parseEntityIds( array $params ) {
+ $ids = $params[self::PARAM_ID];
+ if ( $ids !== null ) {
+ if ( $ids ) {
+ foreach ( $ids as $id ) {
+ try {
+ $entityIds[] =
$this->entityIdParser->parse( $id );
+ } catch ( EntityIdParsingException $e )
{
+ $this->errorReporter->dieError(
+ "Invalid id: $id",
'invalid-entity-id', 0, [ self::PARAM_ID => $id ] );
+ }
+ }
+ return $entityIds;
+ } else {
+ $paramId = self::PARAM_ID;
+ $this->errorReporter->dieError(
+ "If $paramId is specified, it must be
nonempty.", 'no-data' );
+ }
+ } else {
+ return [];
+ }
+ }
+
+ private function parseClaimIds( array $params ) {
+ $ids = $params[self::PARAM_CLAIM_ID];
+ if ( $ids !== null ) {
+ if ( $ids ) {
+ foreach ( $ids as $id ) {
+ try {
+ $claimIds[] =
$this->statementGuidParser->parse( $id );
+ } catch ( EntityIdParsingException $e )
{
+ $this->errorReporter->dieError(
+ "Invalid claim id:
$id", 'invalid-guid', 0, [ self::PARAM_CLAIM_ID => $id ] );
+ }
+ }
+ return $claimIds;
+ } else {
+ $paramClaimId = self::PARAM_CLAIM_ID;
+ $this->errorReporter->dieError(
+ "If $paramClaimId is specified, it must
be nonempty.", 'no-data' );
+ }
+ } else {
+ return [];
+ }
+ }
+
+ private function validateParameters( array $params ) {
+ if ( $params[self::PARAM_CONSTRAINT_ID] !== null
+ && empty( $params[self::PARAM_CONSTRAINT_ID] ) ) {
+ $paramConstraintId = self::PARAM_CONSTRAINT_ID;
+ $this->errorReporter->dieError(
+ "If $paramConstraintId is specified, it must be
nonempty.", 'no-data' );
+ }
+ if ( $params[self::PARAM_ID] === null &&
$params[self::PARAM_CLAIM_ID] === null ) {
+ $paramId = self::PARAM_ID;
+ $paramClaimId = self::PARAM_CLAIM_ID;
+ $this->errorReporter->dieError(
+ "At least one of $paramId, $paramClaimId must
be specified.", 'no-data' );
+ }
+ // contents of PARAM_ID and PARAM_CLAIM_ID are validated by
parse{Entity,Claim}Ids()
+ }
+
+ /**
+ * Converts a flat list of constraint check results
+ * to a nested array structure which can be stored in the ApiResult.
+ * The array is keyed by entity ID, then by property ID,
+ * then by claim ID, and then contains a list of individual results:
+ * { "Q1": { "P1": { "Q1$1a2b...": [ { "status": "compliance", ... }, {
... } ] } } }
+ *
+ * @param CheckResult[] $checkResults
+ * @param string[]|null $entityIds optionally, a list of entity IDs
that should be present in the output even if there are no check results for them
+ *
+ * @return array
+ */
+ private function buildResult( array $checkResults, $entityIds = null ) {
+
+ $constraintReport = array();
+ ApiResult::setArrayType( $constraintReport, 'assoc' );
+
+ // ensure that the report contains the given IDs even if there
are no results for them
+ if ( $entityIds ) {
+ foreach ( $entityIds as $entityId ) {
+ $constraintReport[$entityId] = [];
+ ApiResult::setArrayType(
$constraintReport[$entityId], 'assoc' );
+ }
+ }
+
+ foreach ( $checkResults as $checkResult ) {
+
+ $statement = $checkResult->getStatement();
+
+ $entityId =
$checkResult->getEntityId()->getSerialization();
+ $propertyId =
$checkResult->getPropertyId()->getSerialization();
+ $claimId = $statement->getGuid();
+
+ $result = [
+ 'status' => $checkResult->getStatus(),
+ 'property' =>
$checkResult->getPropertyId()->getSerialization(),
+ 'claim' =>
$checkResult->getStatement()->getGuid(),
+ 'constraint' => array(
+ 'id' => $checkResult->getConstraintId(),
+ 'type' =>
$checkResult->getConstraintName(),
+ 'detail' =>
$checkResult->getParameters(),
+ 'detailHTML' =>
$this->constraintParameterRenderer->formatParameters(
$checkResult->getParameters() )
+ )
+ ];
+ if ( $checkResult->getMessage() ) {
+ $result['message-html'] =
$checkResult->getMessage();
+ }
+
+ $constraintReport[$entityId][$propertyId][$claimId][] =
$result;
+ }
+ return $constraintReport;
+ }
+
+ /**
+ * Returns an array of allowed parameters
+ *
+ * @return array @codeCoverageIgnore
+ */
+ public function getAllowedParams() {
+ return [
+ self::PARAM_ID => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true
+ ],
+ self::PARAM_CLAIM_ID => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true
+ ],
+ self::PARAM_CONSTRAINT_ID => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true
+ ]
+ ];
+ }
+
+ /**
+ * Returns usage examples for this module
+ *
+ * @return array
+ * @codeCoverageIgnore
+ */
+ public function getExamplesMessages() {
+ // TODO Add examples
+ return [];
+ }
+
+}
diff --git a/composer.json b/composer.json
index 073cf85..57d1d21 100644
--- a/composer.json
+++ b/composer.json
@@ -27,6 +27,7 @@
"autoload": {
"psr-4": {
"WikibaseQuality\\ConstraintReport\\": "includes/",
+ "WikibaseQuality\\ConstraintReport\\Api\\": "api/",
"WikibaseQuality\\ConstraintReport\\Specials\\":
"specials/",
"WikibaseQuality\\ConstraintReport\\Tests\\":
"tests/phpunit/",
"WikibaseQuality\\ConstraintReport\\Maintenance\\":
"maintenance/"
@@ -41,4 +42,4 @@
"parallel-lint . --exclude vendor"
]
}
-}
+}
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
index fdc12cf..c01e399 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -28,6 +28,11 @@
"wbqc-constraintreport-result-link-to-constraint": "go to constraint",
"wbqc-constraintreport-no-parameter": "none",
+ "apihelp-wbcheckconstraints-description": "Performs constraint checks
on any entity you want and returns the result.",
+ "apihelp-wbcheckconstraints-param-id": "ID list of the entities to get
the data from. Separate values with '|' or alternative.",
+ "apihelp-wbcheckconstraints-param-claimid": "GUID list identifying a
claim to check a constraint report. Separate values with '|'.",
+ "apihelp-wbcheckconstraints-param-constraintid": "Optional filter to
return only the constraints that have the specified constraint ID",
+
"wbq-subextension-name-wbqc": "Constraints",
"wbqc-violation-header-parameters": "Parameters:",
"wbqc-violations-group": "Constraints",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 089b866..5cd91ed 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -64,5 +64,9 @@
"wbqc-violation-message-type": "Message for violation of Type
constraint. When entity is not of given type.",
"wbqc-violation-message-target-required-claim-property": "Message for
violation of Target required claim constraint. When only a property is given.",
"wbqc-violation-message-target-required-claim-claim": "Message for
violation of Target required claim constraint. When property and value are
given.",
- "wbqc-violation-message-value-type": "Message for violation of Value
type constraint. When value is not of given type."
+ "wbqc-violation-message-value-type": "Message for violation of Value
type constraint. When value is not of given type.",
+ "apihelp-wbcheckconstraints-description":
"{{doc-apihelp-description|wbcheckconstraints}}",
+ "apihelp-wbcheckconstraints-param-id":
"{{doc-apihelp-param|wbcheckconstraints|id}}",
+ "apihelp-wbcheckconstraints-param-claimid":
"{{doc-apihelp-param|wbcheckconstraints|claimid}}",
+ "apihelp-wbcheckconstraints-param-constraintid":
"{{doc-apihelp-param|wbcheckconstraints|constraintid}}"
}
diff --git a/includes/ConstraintCheck/DelegatingConstraintChecker.php
b/includes/ConstraintCheck/DelegatingConstraintChecker.php
index 120d9f1..79c2bed 100644
--- a/includes/ConstraintCheck/DelegatingConstraintChecker.php
+++ b/includes/ConstraintCheck/DelegatingConstraintChecker.php
@@ -7,11 +7,13 @@
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Services\Lookup\EntityLookup;
use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Statement\StatementGuid;
use Wikibase\DataModel\Statement\StatementList;
use Wikibase\DataModel\Statement\StatementListProvider;
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
use WikibaseQuality\ConstraintReport\ConstraintLookup;
use WikibaseQuality\ConstraintReport\Constraint;
+use Wikibase\DataModel\Entity\EntityId;
/**
* Used to start the constraint-check process and to delegate
@@ -50,12 +52,16 @@
/**
* @param EntityLookup $lookup
* @param ConstraintChecker[] $checkerMap
- * @param ConstraintLookup $constraintLookup
+ * @param ConstraintLookup $constraintRepository
*/
- public function __construct( EntityLookup $lookup, array $checkerMap,
ConstraintLookup $constraintLookup ) {
+ public function __construct(
+ EntityLookup $lookup,
+ array $checkerMap,
+ ConstraintLookup $constraintRepository
+ ) {
$this->entityLookup = $lookup;
$this->checkerMap = $checkerMap;
- $this->constraintLookup = $constraintLookup;
+ $this->constraintLookup = $constraintRepository;
}
/**
@@ -79,31 +85,108 @@
}
/**
+ * Starts the whole constraint-check process for entity or constraint
ID on entity.
+ * Statements of the entity will be checked against every constraint
that is defined on the property.
+ *
+ * @param EntityId $entityId
+ * @param array $constraintIds
+ *
+ * @return CheckResult[]
+ *
+ */
+ public function checkAgainstConstraintsOnEntityId( EntityId $entityId,
$constraintIds = null ) {
+
+ $entity = $this->entityLookup->getEntity( $entityId );
+ if ( $entity instanceof StatementListProvider ) {
+ $this->statements = $entity->getStatements();
+ $result = $this->checkEveryStatement(
$this->entityLookup->getEntity( $entityId ), $constraintIds );
+ $output = $this->sortResult( $result );
+ return $output;
+ }
+
+ return [];
+ }
+
+ /**
+ * Starts the whole constraint-check process.
+ * Statements of the entity will be checked against every constraint
that is defined on the claim.
+ *
+ * @param StatementGuid $guid
+ * @param array $constraintIds
+ * @return CheckResult[]
+ */
+ public function checkAgainstConstraintsOnClaimId( StatementGuid $guid,
$constraintIds = null ) {
+
+ $entityId = $guid->getEntityId();
+ $entity = $this->entityLookup->getEntity( $entityId );
+ if ( $entity instanceof StatementListProvider ) {
+ $statement =
$entity->getStatements()->getFirstStatementWithGuid( $guid->getSerialization()
);
+ if ( $statement ) {
+ $result = $this->checkStatement( $entity,
$statement, $constraintIds );
+ $output = $this->sortResult( $result );
+ return $output;
+ }
+ }
+
+ return [];
+ }
+
+ /**
* @param EntityDocument|StatementListProvider $entity
+ * @param string[]|null $constraintIds list of constraints to check (if
null: all constraints)
*
* @return CheckResult[]
*/
- private function checkEveryStatement( EntityDocument $entity ) {
+ private function checkEveryStatement( EntityDocument $entity,
$constraintIds = null ) {
$result = array ();
/** @var Statement $statement */
foreach ( $this->statements as $statement ) {
- if ( $statement->getMainSnak()->getType() !== 'value' )
{
- // skip 'somevalue' and 'novalue' cases, todo:
handle in a better way
- continue;
- }
-
- $constraints =
$this->constraintLookup->queryConstraintsForProperty(
- $statement->getPropertyId()
- );
-
- $result = array_merge( $result,
$this->checkConstraintsForStatementOnEntity( $constraints, $entity, $statement
) );
+ $result = array_merge( $result, $this->checkStatement(
$entity, $statement, $constraintIds ) );
}
return $result;
}
/**
+ *
+ * @param EntityDocument|StatementListProvider $entity
+ * @param Statement $statement
+ * @param string[]|null $constraintIds list of constraints to check (if
null: all constraints)
+ *
+ *
+ * @return CheckResult[]
+ */
+ private function checkStatement( EntityDocument $entity, Statement
$statement, $constraintIds = null ) {
+ $result = array();
+
+ if ( $statement->getMainSnak()->getType() !== 'value' ) {
+ // skip 'somevalue' and 'novalue' cases, todo: handle
in a better way
+ return [];
+ }
+
+ $constraints =
$this->constraintLookup->queryConstraintsForProperty(
+ $statement->getPropertyId()
+ );
+ if ( $constraintIds !== null ) {
+ $constraintsToUse = [];
+ foreach ( $constraints as $constraint ) {
+ if ( in_array( $constraint->getConstraintId(),
$constraintIds ) ) {
+ $constraintsToUse[] = $constraint;
+ }
+ }
+ } else {
+ $constraintsToUse = $constraints;
+ }
+ $result = array_merge(
+ $result,
+ $this->checkConstraintsForStatementOnEntity(
$constraintsToUse, $entity, $statement )
+ );
+
+ return $result;
+ }
+
+ /**
* @param Constraint[] $constraints
* @param EntityDocument|StatementListProvider $entity
* @param Statement $statement
diff --git a/includes/ConstraintParameterRenderer.php
b/includes/ConstraintParameterRenderer.php
new file mode 100644
index 0000000..cbb1778
--- /dev/null
+++ b/includes/ConstraintParameterRenderer.php
@@ -0,0 +1,132 @@
+<?php
+namespace WikibaseQuality\ConstraintReport;
+
+use DataValues;
+use DataValues\DataValue;
+use HTMLForm;
+use Html;
+use InvalidArgumentException;
+use SpecialPage;
+use UnexpectedValueException;
+use ValueFormatters\ValueFormatter;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Entity\EntityIdParsingException;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
+use Wikibase\DataModel\Services\Lookup\EntityLookup;
+use Wikibase\Lib\OutputFormatValueFormatterFactory;
+use Wikibase\Lib\SnakFormatter;
+use Wikibase\Lib\Store\EntityTitleLookup;
+use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
+use Wikibase\Repo\EntityIdHtmlLinkFormatterFactory;
+use Wikibase\Repo\EntityIdLabelFormatterFactory;
+
+/**
+ * Class ConstraintParameterRenderer
+ *
+ * Used to format the constraint values for output.
+ *
+ * @package WikibaseQuality\ConstraintReport
+ * @author BP2014N1
+ * @license GNU GPL v2+
+ */
+
+class ConstraintParameterRenderer{
+
+ /**
+ * Maximum number of displayed values for parameters with multiple ones.
+ *
+ * @var int
+ */
+ const MAX_PARAMETER_ARRAY_LENGTH = 10;
+
+ /**
+ *
+ * @var EntityIdFormatter
+ */
+ private $entityIdLabelFormatter;
+
+ /**
+ * @var ValueFormatter
+ */
+ private $dataValueFormatter;
+
+ /**
+ * @param EntityIdFormatter $entityIdFormatter
+ * @param ValueFormatter $dataValueFormatter
+ */
+ public function __construct( EntityIdFormatter $entityIdFormatter,
+ ValueFormatter
$dataValueFormatter ) {
+
+ $this->entityIdLabelFormatter = $entityIdFormatter;
+ $this->dataValueFormatter = $dataValueFormatter;
+
+ }
+
+ /**
+ * Formats parameter values of constraints.
+ *
+ * @param string|ItemId|PropertyId|DataValue $value
+ *
+ * @return string HTML
+ */
+ public function formatValue( $value ) {
+ if ( is_string( $value ) ) {
+ // Cases like 'Format' 'pattern' or 'minimum'/'maximum'
values, which we have stored as
+ // strings
+ return (htmlspecialchars( $value ));
+ } elseif ( $value instanceof EntityId ) {
+ // Cases like 'Conflicts with' 'property', to which we
can link
+ return $this->entityIdLabelFormatter->formatEntityId(
$value );
+ } else {
+ // Cases where we format a DataValue
+ return $this->dataValueFormatter->format( $value );
+ }
+ }
+
+ /**
+ * Formats constraint parameters.
+ *
+ * @param (string|ItemId|PropertyId|DataValue)[]|null $parameters
+ *
+ * @return string HTML
+ */
+ public function formatParameters( $parameters ) {
+ if ( $parameters === null || count( $parameters ) == 0 ) {
+ return null;
+ }
+
+ $valueFormatter = function ( $value ) {
+ return $this->formatValue( $value );
+ };
+
+ $formattedParameters = array();
+ foreach ( $parameters as $parameterName => $parameterValue ) {
+ $formattedParameterValues = implode( ', ',
+ $this->limitArrayLength( array_map(
$valueFormatter, $parameterValue ) ) );
+ $formattedParameters[] = sprintf( '%s: %s',
$parameterName, $formattedParameterValues );
+ }
+
+ return implode( '; ', $formattedParameters );
+ }
+
+ /**
+ * Cuts an array after n values and appends dots if needed.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ private function limitArrayLength( array $array ) {
+ if ( count( $array ) > self::MAX_PARAMETER_ARRAY_LENGTH ) {
+ $array = array_slice( $array, 0,
self::MAX_PARAMETER_ARRAY_LENGTH );
+ array_push( $array, '...' );
+ }
+
+ return $array;
+ }
+
+}
\ No newline at end of file
diff --git a/includes/ConstraintReportFactory.php
b/includes/ConstraintReportFactory.php
index a2b9c28..270e30e 100644
--- a/includes/ConstraintReportFactory.php
+++ b/includes/ConstraintReportFactory.php
@@ -28,6 +28,7 @@
use
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConnectionCheckerHelper;
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper;
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\TypeCheckerHelper;
+use Wikibase\DataModel\Services\Statement\StatementGuidParser;
class ConstraintReportFactory {
@@ -57,6 +58,11 @@
private $constraintParameterMap;
/**
+ * @var StatementGuidParser
+ */
+ private $statementGuidParser;
+
+ /**
* Returns the default instance.
* IMPORTANT: Use only when it is not feasible to inject an instance
properly.
*
@@ -66,7 +72,7 @@
static $instance = null;
if ( $instance === null ) {
- $instance = new self(
WikibaseRepo::getDefaultInstance()->getEntityLookup() );
+ $instance = new self(
WikibaseRepo::getDefaultInstance()->getEntityLookup(),
WikibaseRepo::getDefaultInstance()->getStatementGuidParser() );
}
return $instance;
@@ -74,9 +80,11 @@
/**
* @param EntityLookup $lookup
+ * @param StatementGuidParser $statementGuidParser
*/
- public function __construct( EntityLookup $lookup ) {
+ public function __construct( EntityLookup $lookup, StatementGuidParser
$statementGuidParser ) {
$this->lookup = $lookup;
+ $this->statementGuidParser = $statementGuidParser;
}
/**
diff --git a/specials/SpecialConstraintReport.php
b/specials/SpecialConstraintReport.php
index 11ec323..6d7c713 100644
--- a/specials/SpecialConstraintReport.php
+++ b/specials/SpecialConstraintReport.php
@@ -13,6 +13,7 @@
use ValueFormatters\FormatterOptions;
use ValueFormatters\ValueFormatter;
use Wikibase\DataModel\Entity\EntityDocument;
+use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityIdParser;
use Wikibase\DataModel\Entity\EntityIdParsingException;
@@ -44,13 +45,6 @@
* @license GNU GPL v2+
*/
class SpecialConstraintReport extends SpecialPage {
-
- /**
- * Maximum number of displayed values for parameters with multiple ones.
- *
- * @var int
- */
- const MAX_PARAMETER_ARRAY_LENGTH = 5;
/**
* Id of the property, that is used to specify constraints on entities.
@@ -95,6 +89,11 @@
* @var DelegatingConstraintChecker
*/
private $constraintChecker;
+
+ /**
+ * @var ConstraintParameterRenderer
+ */
+ private $constraintParameterRenderer;
public static function newFromGlobalState() {
$constraintReportFactory =
ConstraintReportFactory::getDefaultInstance();
@@ -158,6 +157,8 @@
);
$this->constraintChecker = $constraintChecker;
+
+ $this->constraintParameterRenderer = new
ConstraintParameterRenderer( $this->entityIdLabelFormatter,
$this->dataValueFormatter );
}
/**
@@ -378,7 +379,7 @@
// Claim column
$property = $this->entityIdLabelFormatter->formatEntityId(
$result->getPropertyId() );
if ( $result->getMainSnakType() === 'value' ) {
- $value = $this->formatValue( $result->getDataValue() );
+ $value =
$this->constraintParameterRenderer->formatValue( $result->getDataValue() );
} else {
$value = htmlspecialchars( $result->getMainSnakType() );
}
@@ -397,7 +398,7 @@
);
$constraintColumn = $this->buildExpandableElement(
$constraintLink,
- $this->formatParameters( $result->getParameters() ),
+ $this->constraintParameterRenderer->formatParameters(
$result->getParameters() ),
'[...]'
);
@@ -656,67 +657,6 @@
$entityUrl = sprintf( '%s#%s', $title->getLocalURL(),
$propertyId->getSerialization() );
return $entityUrl;
- }
-
- /**
- * Formats values of constraints.
- *
- * @param string|ItemId|PropertyId|DataValue $value
- *
- * @return string HTML
- */
- private function formatValue( $value ) {
- if ( is_string( $value ) ) {
- // Cases like 'Format' 'pattern' or 'minimum'/'maximum'
values, which we have stored as strings
- return ( htmlspecialchars ( $value ) );
- } elseif ( $value instanceof EntityId ) {
- // Cases like 'Conflicts with' 'property', to which we
can link
- return $this->entityIdLabelFormatter->formatEntityId(
$value );
- } else {
- // Cases where we format a DataValue
- return $this->formatDataValues( $value );
- }
- }
-
- /**
- * Formats constraint parameters.
- *
- * @param array $parameters
- *
- * @return string HTML
- */
- private function formatParameters( $parameters ) {
- if ( $parameters === null || count( $parameters ) == 0 ) {
- return null;
- }
-
- $valueFormatter = function ( $value ) {
- return $this->formatValue( $value );
- };
-
- $formattedParameters = array ();
- foreach ( $parameters as $parameterName => $parameterValue ) {
- $formattedParameterValues = implode( ', ',
$this->limitArrayLength( array_map( $valueFormatter, $parameterValue ) ) );
- $formattedParameters[] = sprintf( '%s: %s',
$parameterName, $formattedParameterValues );
- }
-
- return implode( '; ', $formattedParameters );
- }
-
- /**
- * Cuts an array after n values and appends dots if needed.
- *
- * @param array $array
- *
- * @return array
- */
- private function limitArrayLength( $array ) {
- if ( count( $array ) > self::MAX_PARAMETER_ARRAY_LENGTH ) {
- $array = array_slice( $array, 0,
self::MAX_PARAMETER_ARRAY_LENGTH );
- array_push( $array, '...' );
- }
-
- return $array;
}
}
diff --git a/tests/phpunit/Api/CheckConstraintsTest.php
b/tests/phpunit/Api/CheckConstraintsTest.php
new file mode 100644
index 0000000..c54ad1d
--- /dev/null
+++ b/tests/phpunit/Api/CheckConstraintsTest.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Tests\Api;
+
+use ApiTestCase;
+use DataValues\UnknownValue;
+use RequestContext;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\ItemIdParser;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\DataModel\Services\Lookup\InMemoryEntityLookup;
+use Wikibase\DataModel\Services\Statement\StatementGuidParser;
+use Wikibase\DataModel\Services\Statement\StatementGuidValidator;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Statement\StatementList;
+use Wikibase\Repo\EntityIdLabelFormatterFactory;
+use Wikibase\Repo\WikibaseRepo;
+use WikibaseQuality\ConstraintReport\Api\CheckConstraints;
+use WikibaseQuality\ConstraintReport\Constraint;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
+use
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
+use WikibaseQuality\ConstraintReport\Tests\Fake\FakeChecker;
+use WikibaseQuality\ConstraintReport\Tests\Fake\InMemoryConstraintLookup;
+use Wikibase\LanguageFallbackChain;
+use Wikibase\LanguageFallbackChainFactory;
+use Wikibase\DataModel\Services\Lookup\TermLookup;
+use Wikibase\DataModel\Services\Term\TermBuffer;
+use Wikibase\Lib\SnakFormatter;
+use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
+use Wikibase\Lib\OutputFormatValueFormatterFactory;
+use ValueFormatters\FormatterOptions;
+use Wikimedia\Assert\Assert;
+use Language;
+use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
+
+/**
+ * @covers WikibaseQuality\ConstraintReport\Api\CheckConstraints
+ *
+ * @group API
+ * @group Database
+ * @group Wikibase
+ * @group WikibaseAPI
+ *
+ * @group medium
+ *
+ * @license GPL-2.0+
+ */
+class CheckConstraintsTest extends ApiTestCase {
+
+ const NONEXISTENT_ITEM = 'Q99';
+ const NONEXISTENT_CLAIM = 'Q99$does-not-exist';
+
+ private static $oldModuleDeclaration;
+
+ /**
+ * @var InMemoryEntityLookup
+ */
+ private static $entityLookup;
+
+ /**
+ * @var Constraint[]
+ */
+ private static $constraintLookupContents = [];
+
+ /**
+ * @var ConstraintChecker[]
+ */
+ private static $checkerMap = [];
+
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+ global $wgAPIModules;
+
+ self::$oldModuleDeclaration =
$wgAPIModules['wbcheckconstraints'];
+
+ self::$entityLookup = new InMemoryEntityLookup();
+
+ $wgAPIModules['wbcheckconstraints']['factory'] = function (
$main, $name ) {
+ $repo = WikibaseRepo::getDefaultInstance();
+ $factory = new EntityIdLabelFormatterFactory();
+ $termLookup = $repo->getTermLookup();
+ $termBuffer = $repo->getTermBuffer();
+ $languageFallbackChainFactory = new
LanguageFallbackChainFactory();
+ $fallbackLabelDescLookupFactory = new
LanguageFallbackLabelDescriptionLookupFactory( $languageFallbackChainFactory,
$termLookup, $termBuffer );
+ $language = new Language();
+ $labelLookup =
$fallbackLabelDescLookupFactory->newLabelDescriptionLookup( $language );
+
+ $formatterOptions = new FormatterOptions();
+ $factoryFunctions = [];
+ Assert::parameterElementType( 'callable',
$factoryFunctions, '$factoryFunctions' );
+ $formatterOptions->setOption( SnakFormatter::OPT_LANG,
$language->getCode() );
+ $valueFormatterFactory = new
OutputFormatValueFormatterFactory( $factoryFunctions,
$language,$languageFallbackChainFactory );
+ $valueFormatter =
$valueFormatterFactory->getValueFormatter( SnakFormatter::FORMAT_HTML,
$formatterOptions );
+
+ $entityIdParser = new ItemIdParser();
+ $constraintChecker = new DelegatingConstraintChecker(
+ self::$entityLookup,
+ self::$checkerMap,
+ new InMemoryConstraintLookup(
self::$constraintLookupContents )
+ );
+
+ return new CheckConstraints(
+ $main,
+ $name,
+ '',
+ $entityIdParser,
+ new StatementGuidValidator( $entityIdParser ),
+ new StatementGuidParser( $entityIdParser ),
+ $constraintChecker,
+ new ConstraintParameterRenderer(
$factory->getEntityIdFormatter( $labelLookup ), $valueFormatter ),
+ $repo->getApiHelperFactory(
RequestContext::getMain() )
+ );
+ };
+ }
+
+ protected function tearDown() {
+ self::$constraintLookupContents = [];
+ self::$checkerMap = [];
+ parent::tearDown();
+ }
+
+ public static function tearDownAfterClass() {
+ global $wgAPIModules;
+ $wgAPIModules['wbcheckconstraints'] =
self::$oldModuleDeclaration;
+
+ parent::tearDownAfterClass();
+ }
+
+ public function testReportForNonexistentItemIsEmpty() {
+ $result = $this->doRequest(
+ [ CheckConstraints::PARAM_ID => self::NONEXISTENT_ITEM ]
+ );
+
+ $this->assertEmpty(
$result['wbcheckconstraints'][self::NONEXISTENT_ITEM] );
+ }
+
+ public function testReportForNonexistentClaimIsEmpty() {
+ $result = $this->doRequest(
+ [ CheckConstraints::PARAM_CLAIM_ID =>
self::NONEXISTENT_CLAIM ]
+ );
+
+ $this->assertEmpty( $result['wbcheckconstraints'] );
+ }
+
+ public function
testItemExistsAndHasViolation_WillGetOnlyThisViolationInTheResult() {
+ $this->givenItemWithPropertyExists(
+ new ItemId( 'Q1' ),
+ new PropertyId( 'P1' ),
+ 'statement-id'
+ );
+ $this->givenPropertyHasViolation( new PropertyId( 'P1' ) );
+
+ $result = $this->doRequest( [ CheckConstraints::PARAM_ID =>
'Q1' ] );
+
+ $this->assertCount( 1, $result['wbcheckconstraints'] );
+ $resultsForItem =
$result['wbcheckconstraints']['Q1']['P1']['Q1$statement-id'];
+ $this->assertCount( 1, $resultsForItem );
+ $this->assertEquals( CheckResult::STATUS_VIOLATION,
$resultsForItem[0]['status'] );
+ $this->assertEquals( 'P1', $resultsForItem[0]['property'] );
+ }
+
+ public function
testItemWithClaimExistsAndHasViolation_WillGetOnlyThisViolationInTheResult() {
+ $this->givenItemWithPropertyExists(
+ new ItemId( 'Q1' ),
+ new PropertyId( 'P1' ),
+ 'statement-id'
+ );
+ $this->givenPropertyHasViolation( new PropertyId( 'P1' ) );
+
+ $result = $this->doRequest( [ CheckConstraints::PARAM_CLAIM_ID
=> 'Q1$statement-id' ] );
+
+ $this->assertCount( 1, $result['wbcheckconstraints'] );
+ $resultsForItem =
$result['wbcheckconstraints']['Q1']['P1']['Q1$statement-id'];
+ $this->assertCount( 1, $resultsForItem );
+ $this->assertEquals( CheckResult::STATUS_VIOLATION,
$resultsForItem[0]['status'] );
+ $this->assertEquals( 'P1', $resultsForItem[0]['property'] );
+ }
+
+ /**
+ * @param array $params
+ * @return array Array of violations
+ */
+ private function doRequest( array $params ) {
+ $params['action'] = 'wbcheckconstraints';
+ return $this->doApiRequest( $params, [], false, null )[0];
+ }
+
+ private function givenPropertyHasViolation( PropertyId $propertyId ) {
+ self::$checkerMap['violationConstraint'] = new FakeChecker(
CheckResult::STATUS_VIOLATION );
+ self::$constraintLookupContents[] = new Constraint(
+ 'some guid',
+ $propertyId,
+ 'violationConstraint',
+ []
+ );
+ }
+
+ private function givenItemWithPropertyExists(
+ ItemId $itemId,
+ PropertyId $propertyId,
+ $statementId = 'some-id'
+ ) {
+ $item = new Item(
+ $itemId,
+ null,
+ null,
+ new StatementList(
+ [
+ new Statement(
+ new PropertyValueSnak(
$propertyId, new UnknownValue( null ) ),
+ null,
+ null,
+ $itemId->getSerialization() .
'$' . $statementId
+ )
+ ]
+ )
+ );
+ self::$entityLookup->addEntity( $item );
+ }
+}
diff --git a/tests/phpunit/DelegatingConstraintCheckerTest.php
b/tests/phpunit/DelegatingConstraintCheckerTest.php
index baba758..6105b20 100644
--- a/tests/phpunit/DelegatingConstraintCheckerTest.php
+++ b/tests/phpunit/DelegatingConstraintCheckerTest.php
@@ -2,8 +2,13 @@
namespace WikibaseQuality\ConstraintReport\Test\ConstraintChecker;
+use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\ItemIdParser;
+use Wikibase\DataModel\Services\Statement\StatementGuidParser;
+use Wikibase\DataModel\Statement\Statement;
use
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
use WikibaseQuality\ConstraintReport\ConstraintReportFactory;
use WikibaseQuality\Tests\Helper\JsonFileEntityLookup;
@@ -49,10 +54,19 @@
*/
private $lookup;
+ /**
+ * @var StatementGuidParser
+ */
+ private $statementGuidParser;
+
protected function setUp() {
parent::setUp();
- $this->lookup = new JsonFileEntityLookup( __DIR__ );
- $factory = new ConstraintReportFactory( $this->lookup );
+ $this->lookup = $this->createEntityLookup();
+ $this->statementGuidParser = new StatementGuidParser( new
ItemIdParser() );
+ $factory = new ConstraintReportFactory(
+ $this->lookup,
+ $this->statementGuidParser
+ );
$this->constraintChecker = $factory->getConstraintChecker();
// specify database tables used by this test
@@ -275,4 +289,50 @@
$this->assertEquals( 'exception', $result[ 0 ]->getStatus(),
'Should be an exception' );
}
+ public function testCheckAgainstConstraints_ByClaims() {
+ $factory = new ConstraintReportFactory(
+ $this->createEntityLookup(),
+ $this->statementGuidParser
+ );
+ $constraintChecker = $factory->getConstraintChecker();
+
+ $result = $constraintChecker->checkAgainstConstraintsOnClaimId(
+ $this->statementGuidParser->parse(
'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b' ) );
+
+ $this->assertCount( 18, $result, 'Every constraint should be
represented by one result' );
+ }
+
+ public function
testCheckAgainstConstraintsDoesNotCrashWhenResultIsEmpty_ByClaims() {
+ $factory = new ConstraintReportFactory(
+ $this->createEntityLookup(),
+ $this->statementGuidParser
+ );
+ $constraintChecker = $factory->getConstraintChecker();
+
+ $result = $constraintChecker->checkAgainstConstraintsOnClaimId(
+ $this->statementGuidParser->parse(
'Q2$c0f25a6f-9e33-41c8-be34-c86a730ff30b' ) );
+
+ $this->assertCount( 0, $result, 'Should be empty' );
+ }
+
+ public function
testCheckAgainstConstraintsDoesNotCrashWhenClaimDoesNotExist() {
+ $factory = new ConstraintReportFactory(
+ $this->createEntityLookup(),
+ $this->statementGuidParser
+ );
+ $constraintChecker = $factory->getConstraintChecker();
+
+ $result = $constraintChecker->checkAgainstConstraintsOnClaimId(
+ $this->statementGuidParser->parse( 'Q99$does-not-exist'
) );
+
+ $this->assertCount( 0, $result, 'Should be empty' );
+ }
+
+ /**
+ * @return JsonFileEntityLookup
+ */
+ private function createEntityLookup() {
+ return new JsonFileEntityLookup( __DIR__ );
+ }
+
}
diff --git a/tests/phpunit/Q6.json b/tests/phpunit/Q6.json
new file mode 100644
index 0000000..7e9acf8
--- /dev/null
+++ b/tests/phpunit/Q6.json
@@ -0,0 +1,97 @@
+{
+ "id": "Q6",
+ "type": "item",
+ "aliases": {},
+ "labels": {},
+ "claims": {
+ "P1": [
+ {
+ "id": "Q6$01015a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "value",
+ "property": "P1",
+ "datatype": "string",
+ "datavalue": {
+ "value": "foo",
+ "type": "string"
+ }
+ },
+ "type": "statement",
+ "rank": "normal"
+ },
+ {
+ "id": "Q6$01025a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "value",
+ "property": "P1",
+ "datatype": "string",
+ "datavalue": {
+ "value": "foo",
+ "type": "string"
+ }
+ },
+ "type": "statement",
+ "rank": "normal"
+ }
+ ],
+ "P2": [
+ {
+ "id": "Q6$02015a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "value",
+ "property": "P2",
+ "datatype": "string",
+ "datavalue": {
+ "value": "foo",
+ "type": "string"
+ }
+ },
+ "type": "statement",
+ "rank": "normal"
+ }
+ ],
+ "P3": [
+ {
+ "id": "Q6$03015a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "value",
+ "property": "P3",
+ "datatype": "string",
+ "datavalue": {
+ "value": "foo",
+ "type": "string"
+ }
+ },
+ "type": "statement",
+ "rank": "normal"
+ }
+ ],
+ "P4": [
+ {
+ "id": "Q6$04015a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "novalue",
+ "property": "P4"
+ },
+ "type": "statement",
+ "rank": "normal"
+ }
+ ],
+ "P10": [
+ {
+ "id": "Q6$10015a6f-9e33-41c8-be34-c86a730ff30b",
+ "mainsnak": {
+ "snaktype": "value",
+ "property": "P10",
+ "datatype": "string",
+ "datavalue": {
+ "value": "foo",
+ "type": "string"
+ }
+ },
+ "type": "statement",
+ "rank": "normal"
+ }
+ ]
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/341521
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Icf4ec91bc0a06435bcf626e2060008d0d88027a8
Gerrit-PatchSet: 47
Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints
Gerrit-Branch: master
Gerrit-Owner: Olga Bode <[email protected]>
Gerrit-Reviewer: Aleksey Bekh-Ivanov (WMDE) <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Aude <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Jonas Kress (WMDE) <[email protected]>
Gerrit-Reviewer: Lucas Werkmeister (WMDE) <[email protected]>
Gerrit-Reviewer: Olga Bode <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits