Lucas Werkmeister (WMDE) has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/359415 )
Change subject: Add support for importing range parameters ...................................................................... Add support for importing range parameters ConstraintStatementParameterParser gains a new method to parse parameters for a quantity or time range. This is used by the Range and Diff within range constraint types. A message that a parameter must be a value or no value, but not unknown value, is added, and the wbqc-violation-message-range-parameters-needed message is updated to no longer hard-code the constraint type name, since it will be used for both Range and Diff within range. Note that the statement version of the time range parameters does not yet support 'now', since it is not yet clear how that should be modelled (see discussion at [1]). [1]: https://www.wikidata.org/wiki/Help_talk:Property_constraints_portal/Range#special_parameter_.E2.80.9Cnow.E2.80.9D_in_constraint_statements Change-Id: I14bb590d3eb4ea421dd9d169cc737b895e857c65 --- M extension.json M i18n/en.json M i18n/qqq.json M includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php M tests/phpunit/Helper/ConstraintStatementParameterParserTest.php 5 files changed, 297 insertions(+), 2 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseQualityConstraints refs/changes/15/359415/1 diff --git a/extension.json b/extension.json index f4d92e0..3a0e343 100644 --- a/extension.json +++ b/extension.json @@ -190,6 +190,26 @@ "value": "P2305", "description": "The property ID of the 'qualifier of property constraint' property, which specifies the item parameter of an 'item requires claim', 'value requires claim', or 'one of' constraint.", "public": true + }, + "WBQualityConstraintsMinimumQuantityId": { + "value": "P2313", + "description": "The property ID of the 'minimum quantity' property, which specifies the minimum quantity parameter of a 'range' or 'diff within range' constraint.", + "public": true + }, + "WBQualityConstraintsMaximumQuantityId": { + "value": "P2312", + "description": "The property ID of the 'maximum quantity' property, which specifies the maximum quantity parameter of a 'range' or 'diff within range' constraint.", + "public": true + }, + "WBQualityConstraintsMinimumDateId": { + "value": "P2310", + "description": "The property ID of the 'minimum date' property, which specifies the minimum date parameter of a 'range' or 'diff within range' constraint.", + "public": true + }, + "WBQualityConstraintsMaximumDateId": { + "value": "P2311", + "description": "The property ID of the 'maximum date' property, which specifies the maximum date parameter of a 'range' or 'diff within range' constraint.", + "public": true } }, "manifest_version": 2 diff --git a/i18n/en.json b/i18n/en.json index ad5b534..575ab2e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -56,6 +56,7 @@ "wbqc-violation-message-target-entity-must-exist": "The target entity must exist.", "wbqc-violation-message-value-entity-must-exist": "The value entity must exist.", "wbqc-violation-message-parameter-value": "The parameter \"$1\" must have a real value, not \"no value\" or \"unknown value\".", + "wbqc-violation-message-parameter-value-or-novalue": "The parameter \"$1\" must have a real value or \"no value\", not \"unknown value\".", "wbqc-violation-message-parameter-entity": "The value for the parameter \"$1\" must be an entity, not \"$2\".", "wbqc-violation-message-parameter-item": "The value for the parameter \"$1\" must be an item, not \"$2\".", "wbqc-violation-message-parameter-property": "The value for the parameter \"$1\" must be a property, not \"$2\".", @@ -81,7 +82,7 @@ "wbqc-violation-message-qualifier": "The property should only be used as a qualifier.", "wbqc-violation-message-no-qualifiers": "$1 statements should not have any qualifiers.", "wbqc-violation-message-qualifiers": "$2 is not a valid qualifier for $1 – the only valid {{PLURAL:$3|1=qualifier is $5.|2=qualifiers are $5 and $6.|qualifiers are:$4}}", - "wbqc-violation-message-range-parameters-needed": "Properties with values of type \"$1\" with constraint \"Range\" need the parameters \"$2\" and \"$3\".", + "wbqc-violation-message-range-parameters-needed": "Properties with values of type \"$1\" with constraint \"$4\" need the parameters \"$2\" and \"$3\".", "wbqc-violation-message-range": "The value for $1 ($2) should be between $3 and $4 ([$3, $4]).", "wbqc-violation-message-single-value": "This property should only contain a single value. That is, there should only be one statement using this property.", "wbqc-violation-message-symmetric": "$1 should also have the symmetric statement $2 $3.", diff --git a/i18n/qqq.json b/i18n/qqq.json index 66e0535..1bf5333 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -52,6 +52,7 @@ "wbqc-violation-message-target-entity-must-exist": "Message for when an entity is referenced, but it doesn't exist (any more).", "wbqc-violation-message-value-entity-must-exist": "Message for when the property has an entity as its value, but it doesn't exist (any more).", "wbqc-violation-message-parameter-value": "Message for when \"no value\" or \"unknown value\" has been entered as the value of a constraint parameter that must be an actual value. $1 contains the parameter.", + "wbqc-violation-message-parameter-value-or-novalue": "Message for when \"unknown value\" has been entered as the value of a constraint parameter that must be an actual value or \"no value\". $1 contains the parameter.{{Related|wbqc-violation-message-parameter-value}}", "wbqc-violation-message-parameter-entity": "Message for when the value of a constraint parameter must be an entity, but is some other kind of data value. $1 contains the parameter, $2 the data value type.", "wbqc-violation-message-parameter-item": "Message for when the value of a constraint parameter must be an item, but is some other kind of entity. $1 contains the parameter, $2 the data value type.{{Related|wbqc-violation-message-parameter-entity}}", "wbqc-violation-message-parameter-property": "Message for when the value of a constraint parameter must be a property, but is some other kind of entity. $1 contains the parameter, $2 the data value type.{{Related|wbqc-violation-message-parameter-entity}}", @@ -76,7 +77,7 @@ "wbqc-violation-message-qualifier": "Message for violation of Qualifier constraint. When the property is used in a claim.", "wbqc-violation-message-no-qualifiers": "Message for a violation of the “Qualifiers” constraint, when a statement has a qualifier but the property has no permitted qualifiers. This is a special case of {{msg-mw|wbqc-violation-message-qualifiers}}. Parameters:\n* $1 is the qualifier property that is not permitted.{{Related|wbqc-violation-message-qualifiers}}", "wbqc-violation-message-qualifiers": "Message for a violation of the “Qualifiers” constraint, when a statement has a qualifier that is not permitted. Parameters:\n* $1 is the property of the statement.\n* $2 is the qualifier property that is not permitted.\n* $3 is the number of permitted qualifiers. (This number is always greater than zero, since the special case of no permitted qualifiers is handled by the separate message {{msg-mw|wbqc-violation-message-no-qualifiers}}.)\n* $4 is an HTML list of all permitted qualifiers.\n* $5, $6 etc. are the individual permitted qualifiers.{{Related|wbqc-violation-message-no-qualifiers}}", - "wbqc-violation-message-range-parameters-needed": "Message for violation of Range constraint. When min and max parameters of a specific type are needed, but missing.", + "wbqc-violation-message-range-parameters-needed": "Message for when a constraint needs two specific parameters to form a range, but one or both of them are missing. Parameters:\n* $1 is the data type (\"quantity\" or \"time\").\n* $2 is the parameter needed for the lower boundary.\n* $3 is the parameter needed for the upper boundary.\n* $4 is the constraint type name (\"Range\" or \"Diff within range\").", "wbqc-violation-message-range": "Message for a violation of the “Range” constraint, when the value of a statement is smaller or larger than allowed. Parameters:\n* $1 is the property of the statement.\n* $2 is the value of the statement.\n* $3 is the lower bound of the range (inclusive).\n* $4 is the upper bound of the range (inclusive).", "wbqc-violation-message-single-value": "Message for violation of Single value constraint. When more than one value exists.", "wbqc-violation-message-symmetric": "Message for a violation of the “Symmetric” constraint, when the symmetric statement of a statement does not exist. $1, $2 and $3 contain the expected subject entity, property, and target entity of the missing symmetric statement.", diff --git a/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php b/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php index 8b605d6..1c921b3 100644 --- a/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php +++ b/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php @@ -3,7 +3,9 @@ namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper; use Config; +use DataValues\DataValue; use DataValues\StringValue; +use DataValues\UnboundedQuantityValue; use InvalidArgumentException; use Wikibase\DataModel\DeserializerFactory; use Wikibase\DataModel\Deserializers\SnakDeserializer; @@ -15,6 +17,7 @@ use Wikibase\DataModel\Snak\PropertySomeValueSnak; use Wikibase\DataModel\Snak\PropertyValueSnak; use Wikibase\DataModel\Snak\Snak; +use Wikibase\Repo\Parsers\TimeParserFactory; use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue; use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer; @@ -441,4 +444,108 @@ } } + /** + * @param array $parameter Snak serialization + * @param string $parameterId + * @return DataValue|null + */ + private function parseValueOrNoValueParameter( array $snakSerialization, $parameterId ) { + $snak = $this->snakDeserializer->deserialize( $snakSerialization ); + if ( $snak instanceof PropertyValueSnak ) { + return $snak->getDataValue(); + } elseif ( $snak instanceof PropertyNoValueSnak ) { + return null; + } else { + throw new ConstraintParameterException( + wfMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) + ->rawParams( $this->constraintParameterRenderer->formatPropertyId( $parameterId ) ) + ->escaped() + ); + } + } + + private function parseRangeParameterFromStatement( array $constraintParameters, $configKey ) { + $minimumId = $this->config->get( 'WBQualityConstraintsMinimum' . $configKey . 'Id' ); + $maximumId = $this->config->get( 'WBQualityConstraintsMaximum' . $configKey . 'Id' ); + $this->requireSingleParameter( $constraintParameters, $minimumId ); + $this->requireSingleParameter( $constraintParameters, $maximumId ); + return [ + $this->parseValueOrNoValueParameter( $constraintParameters[$minimumId][0], $minimumId ), + $this->parseValueOrNoValueParameter( $constraintParameters[$maximumId][0], $maximumId ) + ]; + } + + private function parseRangeParameterFromTemplate( array $constraintParameters, $type ) { + // the template parameters are always …_quantity, see T164087 + $this->requireSingleParameter( $constraintParameters, 'minimum_quantity' ); + $this->requireSingleParameter( $constraintParameters, 'maximum_quantity' ); + if ( $type === 'quantity' ) { + $min = UnboundedQuantityValue::newFromNumber( $constraintParameters['minimum_quantity'] ); + $max = UnboundedQuantityValue::newFromNumber( $constraintParameters['maximum_quantity'] ); + } else { + $timeParser = ( new TimeParserFactory() )->getTimeParser(); + $minStr = $constraintParameters['minimum_quantity']; + $maxStr = $constraintParameters['maximum_quantity']; + $now = gmdate( '+Y-m-d\T00:00:00\Z' ); + if ( $minStr === 'now' ) { + $minStr = $now; + } + if ( $maxStr === 'now' ) { + $maxStr = $now; + } + $min = $timeParser->parse( $minStr ); + $max = $timeParser->parse( $maxStr ); + } + return [ $min, $max ]; + } + + /** + * @param array $constraintParameters + * @param string $constraintTypeName + * @param string $type 'quantity' or 'time' + * @throws ConstraintParameterException if the parameter is invalid or missing + * @return DataValue[] a pair of two quantity-type data values, either of which may be null to signify an open range + */ + public function parseRangeParameter( array $constraintParameters, $constraintTypeName, $type ) { + switch ( $type ) { + case 'quantity': + $configKey = 'Quantity'; + break; + case 'time': + $configKey = 'Date'; + break; + default: + throw new ConstraintParameterException( + wfMessage( 'wbqc-violation-message-value-needed-of-types-2' ) + ->rawParams( + wfMessage( 'datatypes-type-quantity' )->escaped(), + wfMessage( 'datatypes-type-time' )->escaped() + ) + ->escaped() + ); + } + $minimumId = $this->config->get( 'WBQualityConstraintsMinimum' . $configKey . 'Id' ); + $maximumId = $this->config->get( 'WBQualityConstraintsMaximum' . $configKey . 'Id' ); + if ( array_key_exists( $minimumId, $constraintParameters ) && + array_key_exists( $maximumId, $constraintParameters ) ) { + return $this->parseRangeParameterFromStatement( $constraintParameters, $configKey ); + } elseif ( array_key_exists( 'minimum_quantity', $constraintParameters ) && + array_key_exists( 'maximum_quantity', $constraintParameters ) ) { + // the template parameters are always …_quantity, see T164087 + return $this->parseRangeParameterFromTemplate( $constraintParameters, $type ); + } else { + throw new ConstraintParameterException( + wfMessage( 'wbqc-violation-message-range-parameters-needed' ) + ->params( $constraintTypeName ) + ->rawParams( + wfMessage( 'datatypes-type-' . $type )->escaped(), + $this->constraintParameterRenderer->formatPropertyId( $minimumId ), + $this->constraintParameterRenderer->formatPropertyId( $maximumId ) + ) + ->params( $constraintTypeName ) + ->escaped() + ); + } + } + } diff --git a/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php b/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php index 15b777c..7a789ab 100644 --- a/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php +++ b/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php @@ -3,6 +3,8 @@ namespace WikibaseQuality\ConstraintReport\Test\Helper; use DataValues\StringValue; +use DataValues\TimeValue; +use DataValues\UnboundedQuantityValue; use Wikibase\DataModel\Entity\EntityIdValue; use Wikibase\DataModel\Entity\ItemId; use Wikibase\DataModel\Entity\PropertyId; @@ -484,4 +486,168 @@ $this->assertEquals( [], $parsed ); } + public function testParseRangeParameterQuantityBounded() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); + $propertyId = new PropertyId( 'P1' ); + $min = UnboundedQuantityValue::newFromNumber( 13.37 ); + $max = UnboundedQuantityValue::newFromNumber( 42 ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] + ], + '', + 'quantity' + ); + + $this->assertEquals( [ $min, $max ], $parsed ); + } + + public function testParseRangeParameterQuantityLeftOpen() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); + $propertyId = new PropertyId( 'P1' ); + $max = UnboundedQuantityValue::newFromNumber( 42 ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] + ], + '', + 'quantity' + ); + + $this->assertEquals( [ null, $max ], $parsed ); + } + + public function testParseRangeParameterQuantityRightOpen() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); + $propertyId = new PropertyId( 'P1' ); + $min = UnboundedQuantityValue::newFromNumber( 13.37 ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ] + ], + '', + 'quantity' + ); + + $this->assertEquals( [ $min, null ], $parsed ); + } + + public function testParseRangeParameterQuantityFullyOpen() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); + $propertyId = new PropertyId( 'P1' ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ] + ], + '', + 'quantity' + ); + + $this->assertSame( [ null, null ], $parsed ); + } + + public function testParseRangeParameterQuantityFromTemplate() { + $min = UnboundedQuantityValue::newFromNumber( 13.37 ); + $max = UnboundedQuantityValue::newFromNumber( 42 ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + 'minimum_quantity' => '13.37', + 'maximum_quantity' => '42' + ], + '', + 'quantity' + ); + + $this->assertEquals( [ $min, $max ], $parsed ); + } + + public function testParseRangeParameterQuantitySomeValue() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); + $propertyId = new PropertyId( 'P1' ); + + $this->assertThrowsConstraintParameterException( + 'parseRangeParameter', + [ + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertySomeValueSnak( $propertyId ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertySomeValueSnak( $propertyId ) ) ] + ], + 'constraint', + 'quantity' + ], + 'wbqc-violation-message-parameter-value-or-novalue' + ); + } + + public function testParseRangeParameterTimeBounded() { + $config = $this->getDefaultConfig(); + $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); + $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); + $propertyId = new PropertyId( 'P1' ); + $calendar = 'http://www.wikidata.org/entity/Q1985727'; + $min = new TimeValue( '+1789-05-08T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_YEAR, $calendar ); + $max = new TimeValue( '+1955-02-05T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_YEAR, $calendar ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + $minimumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], + $maximumId => [ $this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] + ], + '', + 'time' + ); + + $this->assertEquals( [ $min, $max ], $parsed ); + } + + public function testParseRangeParameterTimeFromTemplate() { + $calendar = 'http://www.wikidata.org/entity/Q1985727'; + $min = new TimeValue( '+1753-00-00T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_YEAR, $calendar ); + $max = new TimeValue( gmdate( '+Y-m-d\T00:00:00\Z' ), 0, 0, 0, TimeValue::PRECISION_DAY, $calendar ); + + $parsed = $this->getConstraintParameterParser()->parseRangeParameter( + [ + 'minimum_quantity' => '1753', + 'maximum_quantity' => 'now' + ], + '', + 'time' + ); + + $this->assertEquals( [ $min, $max ], $parsed ); + } + + public function testParseRangeParameterMissingParameters() { + foreach ( [ 'quantity', 'time' ] as $type ) { + $this->assertThrowsConstraintParameterException( + 'parseRangeParameter', + [ + [], + 'constraint', + $type + ], + 'wbqc-violation-message-range-parameters-needed' + ); + } + } + } -- To view, visit https://gerrit.wikimedia.org/r/359415 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I14bb590d3eb4ea421dd9d169cc737b895e857c65 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints Gerrit-Branch: master Gerrit-Owner: Lucas Werkmeister (WMDE) <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
