User:Ugochimobi/PropertyCreator.js

// /* jshint maxerr: 999 */

// After creating, when marking proposal as done, for easy of copying // This new script will help to simplify property creation.

// notes: // convert "instance of" to dropdown? Always use Q456? ("Gratisdata property for an identifier") // regex to convert examples from the page: `Example.*?\((.*?)\) → ` -> `$1|` $( => { const PropertyCreator = {}; window.PropertyCreator = PropertyCreator;

PropertyCreator.config = { version: 'dev', author: 'Ugochimobi', createSummary: 'Creating new property', // CONFIG for api calls // "property proposal discussion" propertyProposalDiscussionProp: 'P478', // "Gratisdata property example" propertyExampleProp: 'P195', // Expected completeness. New properties are almost always created // as "always incomplete" or "eventually complete" // "expected completeness" // "always incomplete" // "eventually complete" completenessProp: "P209", completenessAlwaysIncomplete: "Q2110", completenessEventuallyComplete: "Q548", // "Gratisdata item of this property" subjectItemProp: 'P262', // Inverse, used to link back // "Gratisdata property" gratisdataPropertyProp: 'P165', // "formatter URL" formatterURLProp: 'P8', // "format as a regular expression" regexFormatProperty: 'P148', // CONSTRAINTS // Overall property, "property constraint" constraintPropertyProp: 'P142', // regex "format constraint" regexFormatConstraint: 'Q673', // "Gratispaideia import URL" gratispaideiaImportURLProp: 'P394', // For now only works with main scope // "property scope constraint" // qualifier for the constraint, "property scope" // scope: main value, "as main value" propertyScopeConstraint: 'Q779', propertyScopeConstraintProp: 'P338', propertyScopeAsMain: 'Q793', // For now only works with items // "allowed entity types constraint" // "item of property constraint" // "Wikibase item" allowedEntityTypesConstraint: 'Q781', itemOfPropertyConstraint: 'P340', entityTypeItem: 'Q799', // "instance of" instanceOfProp: 'P3', // "distinct values constraint", Q21502410 in prod, ??? in test distinctValuesConstraint: 'Q21502410', // "single value constraint", Q19474404 in prod, ??? in test singleValueConstraint: 'Q19474404', // "source website for the property", P1896 in prod, P94562 in test sourceWebsiteProp: 'P1896' };

PropertyCreator.checkDomain = function { // Use different configuration on test.wikidata.org if ( mw.config.get( 'wgDBname') !== 'testwikidatawiki' ) { return; }	var testWikidataConfig = { propertyProposalDiscussionProp: 'P94264', propertyExampleProp: 'P48077', completenessProp: "P95238", completenessAlwaysIncomplete: "Q211837", completenessEventuallyComplete: "Q213515", subjectItemProp: 'P94574', wikidataPropertyProp: 'P678', formatterURLProp: 'P368', regexFormatProperty: 'P51065', constraintPropertyProp: 'P51064', regexFormatConstraint: 'Q100086', wikimediaImportURLProp: 'P77057', propertyScopeConstraint: 'Q187953', propertyScopeConstraintProp: 'P84130', propertyScopeAsMain: 'Q187967', allowedEntityTypesConstraint: 'Q187951', itemOfPropertyConstraint: 'P76946', entityTypeItem: 'Q187962', instanceOfProp: 'P82', sourceWebsiteProp: 'P94562' };	$.extend( PropertyCreator.config, testWikidataConfig ); var e = PropertyCreator.elements; // Only for testing/development; label needs to be changed (eg add a number at the end) e.pLabel.setValue( 'ProperlyLabel' ); e.pDescription.setValue( 'PropertyDescription' ); e.instanceOf.setValue( 'Q213516' ); e.proposalDiscussion.setValue( 'https://www.wikidata.org/wiki/Wikidata:Property_proposal/Supreme_Court_docket_number' ); e.subjectItem.setValue( 'Q34734' ); e.sourceWebsite.setValue( 'https://www.wikidata.org/wiki/Property:P7063' ); e.examplesText.setValue( "Q199546|foo\nQ57403|bar" ); e.formatterURL.setValue( 'https://caselaw.findlaw.com/search.html?search_type=docket&court=us-supreme-court&text=$1' ); e.valueRegex.setValue( '\\d+' ); };

PropertyCreator.onErrHandler = function { // Shared error handler alert( 'Something went wrong' ); console.log( arguments ); };

PropertyCreator.init = function { window.document.title = 'PropertyCreator script'; PropertyCreator.createElements; PropertyCreator.checkDomain; var fieldSet = new OO.ui.FieldsetLayout( { 		label: 'Helper to easily create new properties'	} ); var e = PropertyCreator.elements; fieldSet.addItems( [		e.pLabelLayout,		e.pDescriptionLayout,		e.pDataTypesLayout,		e.proposalDiscussionLayout,		e.instanceOfLayout,		e.allowedEntityTypeLayout,		e.subjectItemLayout,		e.sourceWebsiteLayout,		e.propertyScopeLayout,		e.expectedCompletenessLayout,		e.examplesTextLayout,		e.formatterURLLayout,		e.valueRegexLayout,		e.distinctValuesConstraintLayout,		e.singleValueConstraintLayout,		e.submitButtonLaoyout	] ); $('#mw-content-text').empty.append(		fieldSet.$element	); e.submitButton.on(		'click',		PropertyCreator.onSubmit	); };

PropertyCreator.done = function ( propId ) { console.log( 'DONE' ); console.log( 'Property created at: ' + propId ); };

PropertyCreator.onSubmit = function { console.log( 'Submitted!' ); var e = PropertyCreator.elements; PropertyCreator.createProperty(		e.pLabel.getValue,		e.pDescription.getValue,		e.pDataTypes.getValue	); };

PropertyCreator.util = {}; PropertyCreator.util.getSnakValue = function ( input ) { // Items (Qxxxx) if ( input.match( /^Q\d+$/ ) ) { return '{"entity-type":"item","numeric-id":' + input.replace('Q', '') + '}'; }	// Properties (Pxxxx) if ( input.match( /^P\d+$/ ) ) { return '{"entity-type":"property","numeric-id":' + input.replace('P', '') + '}'; }	// A string return '"' + input + '"'; };

PropertyCreator.util.addClaim = function ( claimInfo ) { // claimInfo is an object with `claimEntity`, `claimProperty`, and `claimValue` all set // as well as `addRef` for whether the discussion should be reference // claimQualifier can be false, or an object with a `prop` and `value` console.log( 'Converting claim value to snak, started as ' + claimInfo.claimValue ); claimInfo.claimValue = PropertyCreator.util.getSnakValue( claimInfo.claimValue ); console.log(		'Adding claim to ' + claimInfo.claimEntity +		': property ' + claimInfo.claimProperty +		' has value ' + claimInfo.claimValue	); return new Promise( function ( resolve ) {		new mw.Api.postWithEditToken( { action: 'wbcreateclaim', snaktype: 'value', entity: claimInfo.claimEntity, property: claimInfo.claimProperty, value: claimInfo.claimValue } ).then( function ( response ) { console.log( response ); // Good result var claimId = response.claim.id; var qualifierPromise, refPromise; if ( claimInfo.claimQualifier && claimInfo.claimQualifier !== false ) { qualifierPromise = PropertyCreator.addQualifier(						claimId,						claimInfo.claimQualifier.prop,						claimInfo.claimQualifier.value					); } else { qualifierPromise = Promise.resolve( true ); }				qualifierPromise.then(					function ( response2 ) {						console.log( response2 ); // Good result						if ( claimInfo.addRef ) {							refPromise = PropertyCreator.addDiscussionReference( claimId );						} else {							refPromise = Promise.resolve( true );						}						refPromise.then( function ( response3 ) { console.log( response3 ); // Good result resolve; },							PropertyCreator.onErrHandler );					},					PropertyCreator.onErrHandler				); },			PropertyCreator.onErrHandler );	} ); };

PropertyCreator.createProperty = function ( labelValue, descriptionValue, dataType ) { var propertyData = { 'labels': { 'en': { 'language': 'en', 'value': labelValue } }, 'descriptions': { 'en': { 'language': 'en', 'value': descriptionValue } }, 'datatype': dataType };	var propertyDataStr = JSON.stringify( propertyData ); console.log( propertyData, propertyDataStr ); new mw.Api.postWithEditToken( {		'action': 'wbeditentity',		'new': 'property',		'summary': PropertyCreator.config.createSummary,		'data': propertyDataStr	} ).then(		function ( response ) {			console.log( response ); // Good result			var propertyId = response.entity.id;			// No need to wait for this to be done			PropertyCreator.createPropertyTalk( propertyId );			PropertyCreator.addInstanceOf( propertyId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.createPropertyTalk = function ( propId ) { new mw.Api.postWithEditToken( {		action: 'edit',		title: 'Property talk:' + propId,		text: '',		summary: 'Create with '	} ).then(		function ( response ) {			console.log( response ); // Good result		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addInstanceOf = function ( propId ) { var instanceOfValue = PropertyCreator.elements.instanceOf.getValue; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.instanceOfProp,		claimValue: instanceOfValue,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.addPropertyExamplesInit( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addPropertyExamplesInit = function ( propId ) { console.log( 'Adding property examples for new property with id ' + propId ); var examplesStr = PropertyCreator.elements.examplesText.getValue; var examples = examplesStr.split("\n"); PropertyCreator.addPropertyExamplesRecursive( propId, examples ); };

PropertyCreator.addPropertyExamplesRecursive = function ( propertyId, examples ) { var currentExample = examples.pop; var exampleParams = currentExample.split('|'); var exampleItem = exampleParams[0]; var exampleValue = exampleParams[1]; PropertyCreator.addPropertyExample( exampleItem, propertyId, exampleValue ).then( function {		PropertyCreator.documentPropertyExample( exampleItem, propertyId, exampleValue ).then( function  { if ( examples.length !== 0 ) { PropertyCreator.addPropertyExamplesRecursive( propertyId, examples ); } else { // Continue to the next step PropertyCreator.documentSubjectItem( propertyId ); }		} );	} ); };

PropertyCreator.addPropertyExample = function ( item, propId, value ) { console.log( 'Setting examples: item ' + item + ' has property value ' + value ); // THIS ONLY WORKS FOR EXTERNAL LINKS FOR NOW (probably) return PropertyCreator.util.addClaim( {		claimEntity: item,		claimProperty: propId,		claimValue: value,		addRef: true	} ); }; PropertyCreator.documentPropertyExample = function ( item, propId, value ) { console.log( 'Document examples: item ' + item + ' has property value ' + value ); // THIS ONLY WORKS FOR EXTERNAL LINKS FOR NOW (probably) return PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.propertyExampleProp,		claimValue: item,		claimQualifier: {			prop: propId,			value: value		},		addRef: true	} ); };

PropertyCreator.documentSubjectItem = function ( propId ) { // Link property to subject item subjectItemValue = PropertyCreator.elements.subjectItem.getValue; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.subjectItemProp,		claimValue: subjectItemValue,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.documentSubjectProperty( propId, subjectItemValue );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.documentSubjectProperty = function ( propId, subjectItemValue ) { // Link subject item back to property PropertyCreator.util.addClaim( {		claimEntity: subjectItemValue,		claimProperty: PropertyCreator.config.wikidataPropertyProp,		claimValue: propId,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.documentDiscussion( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.documentDiscussion = function ( propId ) { PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.propertyProposalDiscussionProp,		claimValue: PropertyCreator.elements.proposalDiscussion.getValue,		addRef: false	} ).then(		function ( response ) {			PropertyCreator.documentSourceWebsite( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.documentSourceWebsite = function ( propId ) { var sourceWebsite = PropertyCreator.elements.sourceWebsite.getValue; if ( sourceWebsite === '' ) { // No source website set, move on		console.log( 'Skipping source website handling, no source website set set' ); PropertyCreator.documentExpectedCompleteness( propId ); return; }	PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.sourceWebsiteProp,		claimValue: sourceWebsite,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.documentExpectedCompleteness( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.documentExpectedCompleteness = function ( propId ) { if ( PropertyCreator.elements.expectedCompleteness.getValue === 'unknown' ) { console.log( 'Skipping expected completeness, not listed' ); PropertyCreator.documentFormatterURL( propId ); return; }	var expectedCompletenessValue; if ( PropertyCreator.elements.expectedCompleteness.getValue === 'alwaysIncomplete' ) { expectedCompletenessValue = PropertyCreator.config.completenessAlwaysIncomplete; } else { expectedCompletenessValue = PropertyCreator.config.completenessEventuallyComplete; }	PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.completenessProp,		claimValue: expectedCompletenessValue,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.documentFormatterURL( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.documentFormatterURL = function ( propId ) { var formatterURL = PropertyCreator.elements.formatterURL.getValue; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.formatterURLProp,		claimValue: formatterURL,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.addRegex( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addRegex = function ( propId ) { // Adds "format as a regular expression" and calls addRegexConstraint var propertyRegex = PropertyCreator.elements.valueRegex.getValue; if ( propertyRegex === '' ) { // No regex set, move on		console.log( 'Skipping regex handling, no regex set' ); PropertyCreator.addScopeConstraint( propId ); return; }	propertyRegex = propertyRegex.replace( /\\/g, '\\\\' ); console.log( 'Adding regex: ' + propertyRegex ); PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: PropertyCreator.config.regexFormatProperty,		claimValue: propertyRegex,		addRef: true	} ).then(		function ( response ) {			PropertyCreator.addRegexConstraint( propId, propertyRegex );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addRegexConstraint = function ( propId, propertyRegex ) { var cfg = PropertyCreator.config; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: cfg.constraintPropertyProp,		claimValue: cfg.regexFormatConstraint,		claimQualifier: {			prop: cfg.regexFormatProperty,			value: propertyRegex		},		addRef: true	} ).then(		function ( response ) {			PropertyCreator.addScopeConstraint( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addScopeConstraint = function ( propId ) { var cfg = PropertyCreator.config; var scopeConstraintValue; if ( PropertyCreator.elements.propertyScope.getValue === 'asMain' ) { // Only option for now scopeConstraintValue = cfg.propertyScopeAsMain; }	PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: cfg.constraintPropertyProp,		claimValue: cfg.propertyScopeConstraint,		claimQualifier: {			prop: cfg.propertyScopeConstraintProp,			value: scopeConstraintValue		},		addRef: true	} ).then(		function ( response ) {			PropertyCreator.addEntityTypeConstraint( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.addEntityTypeConstraint = function ( propId ) { var cfg = PropertyCreator.config; var typeConstraintValue; if ( PropertyCreator.elements.allowedEntityType.getValue === 'typeItem' ) { // Only option for now typeConstraintValue = cfg.entityTypeItem; }	PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: cfg.constraintPropertyProp,		claimValue: cfg.allowedEntityTypesConstraint,		claimQualifier: {			prop: cfg.itemOfPropertyConstraint,			value: typeConstraintValue		},		addRef: true	} ).then(		function ( response ) {			PropertyCreator.maybeAddDistinctValuesConstraint( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.maybeAddDistinctValuesConstraint = function ( propId ) { if ( PropertyCreator.elements.distinctValuesConstraint.isSelected === false ) { // Skip PropertyCreator.maybeAddSingleValueConstraint( propId ); return; }	var cfg = PropertyCreator.config; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: cfg.constraintPropertyProp,		claimValue: cfg.distinctValuesConstraint,		addRef: false	} ).then(		function ( response ) {			// Added because its an external identifier, usually not discussed,			// so no reference, and doesn't use any qualifiers			PropertyCreator.maybeAddSingleValueConstraint( propId );		},		PropertyCreator.onErrHandler	); };

PropertyCreator.maybeAddSingleValueConstraint = function ( propId ) { if ( PropertyCreator.elements.singleValueConstraint.isSelected === false ) { // Skip PropertyCreator.done( propId ); return; }	var cfg = PropertyCreator.config; PropertyCreator.util.addClaim( {		claimEntity: propId,		claimProperty: cfg.constraintPropertyProp,		claimValue: cfg.singleValueConstraint,		addRef: false	} ).then(		function ( response ) {			// Added because its an external identifier, usually not discussed,			// so no reference, and doesn't use any qualifiers			PropertyCreator.done( propId );		},		PropertyCreator.onErrHandler	); // TODO "domain" / "value type constraint" };

PropertyCreator.addDiscussionReference = function ( claimNeedingReference ) { console.log( 'Documenting the discussion as the reference for a claim' ); var reference = { referenceProp: [ { 'snaktype': 'value', 'property': PropertyCreator.config.wikimediaImportURLProp, 'datavalue': { 'type': 'string', 'value': PropertyCreator.elements.proposalDiscussion.getValue }		} ]	};	var referenceStr = JSON.stringify( reference ); console.log( 'Documenting the discussion as a reference for the claim', claimNeedingReference, reference, referenceStr ); return new mw.Api.postWithEditToken( {		action: 'wbsetreference',		statement: claimNeedingReference,		snaks: referenceStr	} ); };

PropertyCreator.addQualifier = function ( claimId, qualifierProp, qualifierValue ) { console.log( 'Converting qualifier value to snak, started as ' + qualifierValue ); qualifierValue = PropertyCreator.util.getSnakValue( qualifierValue ); console.log(		'Adding a qualifier for claim ' + claimId +		': property ' + qualifierProp + ' with value ' + qualifierValue	); return new mw.Api.postWithEditToken( {		action: 'wbsetqualifier',		claim: claimId,		property: qualifierProp,		value: qualifierValue,		snaktype: 'value'	} ); };

PropertyCreator.elements = {}; PropertyCreator.createElements = function { var e = {}; e.pLabel = new OO.ui.TextInputWidget; e.pLabelLayout = new OO.ui.FieldLayout( e.pLabel, { label: 'Property label' } ); e.pDescription = new OO.ui.TextInputWidget; e.pDescriptionLayout = new OO.ui.FieldLayout( e.pDescription, { label: 'Property description' } ); var validDataTypes = [ 'external-id' ]; var dataTypeOptions = validDataTypes.map(		function ( type ) {			return { data: type, label: type };		}	); e.pDataTypes = new OO.ui.DropdownInputWidget( { options: dataTypeOptions } ); e.pDataTypesLayout = new OO.ui.FieldLayout( e.pDataTypes, { label: 'Data type' } ); e.proposalDiscussion = new OO.ui.TextInputWidget; e.proposalDiscussionLayout = new OO.ui.FieldLayout( e.proposalDiscussion, { label: 'Proposal discussion' } ); e.instanceOf = new OO.ui.TextInputWidget; e.instanceOfLayout = new OO.ui.FieldLayout( e.instanceOf, { label: 'Instance of' } ); e.allowedEntityType = new OO.ui.RadioSelectInputWidget( {		options: [			{ data: 'typeItem', label: 'Wikibase item' }		]	} ); e.allowedEntityTypeLayout = new OO.ui.FieldLayout( e.allowedEntityType, { label: 'Allowed entity type' } ); e.subjectItem = new OO.ui.TextInputWidget; e.subjectItemLayout = new OO.ui.FieldLayout( e.subjectItem, { label: 'Subject item for this property' } ); e.sourceWebsite = new OO.ui.TextInputWidget; e.sourceWebsiteLayout = new OO.ui.FieldLayout( e.sourceWebsite, { label: 'Source website for this property' } ); e.propertyScope = new OO.ui.RadioSelectInputWidget( {		options: [			{ data: 'asMain', label: 'As main value' }		]	} ); e.propertyScopeLayout = new OO.ui.FieldLayout( e.propertyScope, { label: 'Property scope' } ); e.expectedCompleteness = new OO.ui.RadioSelectInputWidget( {		options: [			{ data: 'alwaysIncomplete', label: 'Always incomplete' },       	{ data: 'eventuallyComplete', label: 'Eventually complete'},        	{ data: 'unknown', label: 'Unknown (skip)' }		]	} ); e.expectedCompletenessLayout = new OO.ui.FieldLayout( e.expectedCompleteness, { label: 'Expected completeness' } ); e.examplesText = new OO.ui.MultilineTextInputWidget( { rows: 4 } ); e.examplesTextLayout = new OO.ui.FieldLayout(		e.examplesText,		{ label: 'Examples (one per line, separate item and value with |)' }	); e.formatterURL = new OO.ui.TextInputWidget; e.formatterURLLayout = new OO.ui.FieldLayout( e.formatterURL, { label: 'Formatter URL' } ); e.valueRegex = new OO.ui.TextInputWidget; e.valueRegexLayout = new OO.ui.FieldLayout( e.valueRegex, { label: 'Regex for values' } ); e.distinctValuesConstraint = new OO.ui.CheckboxInputWidget; e.distinctValuesConstraintLayout = new OO.ui.FieldLayout( e.distinctValuesConstraint, { label: 'Distinct values constraint' } ); e.singleValueConstraint = new OO.ui.CheckboxInputWidget; e.singleValueConstraintLayout = new OO.ui.FieldLayout( e.singleValueConstraint, { label: 'Single value constraint' } ); e.submitButton = new OO.ui.ButtonInputWidget( { 		label: 'Create',		flags: [			'primary',			'progressive'		]	} ); e.submitButtonLaoyout = new OO.ui.FieldLayout( e.submitButton ); PropertyCreator.elements = e; };

});

mw.loader.using(	[ 'mediawiki.util', 'mediawiki.api', 'oojs-ui-widgets', 'oojs-ui-windows' ],	function {		$( document ).ready(  => { if (				mw.config.get( 'wgNamespaceNumber' ) === -1 &&				mw.config.get( 'wgTitle' ) === 'BlankPage/PropertyCreator'			) { window.PropertyCreator.init; } else { mw.util.addPortletLink(					'p-tb',					'/wiki/Special:BlankPage/PropertyCreator',					'PropertyCreator helper form'				); }		} );	} ); //