Page MenuHomePhabricator

Maintenance script to back-fill proposer votes
Closed, ResolvedPublicBUG REPORT

Description

In T406670 we added the automatic addition of a support vote for the proposer, when a wish is created.

Wishes created prior to that being released do not all have a vote by the proposer (although some do, where the proposer added it manually), so a maintenance script should be created that determines which wishes are missing these votes and adds them.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript
mikez-WMF triaged this task as Medium priority.Feb 3 2026, 8:21 PM
MusikAnimal subscribed.

I can write a quick in-browser script to do this.

197 votes added: https://meta.wikimedia.org/w/index.php?title=Special:Contributions/MusikAnimal_(WMF)&target=MusikAnimal+%28WMF%29&dir=prev&offset=20260305000125&limit=196

The proposers were pinged in the edit summaries, should they want to rescind their vote for whatever reason, or edit it to add a comment.

I used this script:

const api = new mw.Api();

const pages = await api.get( {
	action: 'query',
	list: 'communityrequests-wishes',
	crwstatuses: "community-opportunity|in-progress|long-term-opportunity|near-term-opportunity|prioritized|under-review",
	crwprop: 'proposer|created',
	crwend: "2025-12-17T00:00:00.000Z",
	crwlimit: 500
} );

for ( const page of pages.query['communityrequests-wishes'] ) {
	const { proposer, created, crwtitle } = page;
	if ( proposer.match( /(?:\-WMF|\(WMF\))$/ ) ) {
		console.log( `Skipping staff vote ${proposer} ${crwtitle}` );
		continue;
	}
	const votesContent = ( await api.get( {
		action: 'query',
		prop: 'revisions',
		titles: `${ crwtitle }/Votes`,
		rvprop: 'content',
		formatversion: 2
	} ) )?.query?.pages?.[0]?.revisions?.[0]?.content || '';

	const regexp = new RegExp( `\\|username=${proposer}\\|` );
	if ( votesContent.match( regexp ) ) {
		console.log( `Already voted: ${proposer} ${crwtitle}` );
	} else {
		api.edit( `${ crwtitle }/Votes`, ( rev ) => {
			return {
				text: `{{#CommunityRequests:vote|username=${proposer}|comment=|timestamp=${created}}}\n` + votesContent,
				summary: `Adding support vote on behalf of [[User:${proposer}|${proposer}]] ([[phab:T415231]])`
			}
		} );
	}
}

We're not done yet though. Through this I discovered a bug: /Votes pages can't manually be created by Wishlist managers, even though they can be edited. There's no need for that functionality outside this one-off use case, so it's probably not worth fixing. A proper PHP maintenance script would have gotten around this.

It's hacky but I guess what I'll do is add my own vote to the remaining wishes, so that the /Votes page is created. Then I'll rescind the vote (staff don't vote anyway!), and finally make the vote on behalf of the proposer.

Okay, I think we're done! The revised (and corrected script):

const api = new mw.Api();

const pages = await api.get( {
	action: 'query',
	list: 'communityrequests-wishes',
	crwstatuses: "community-opportunity|in-progress|long-term-opportunity|near-term-opportunity|prioritized|under-review",
	crwprop: 'proposer|created',
	crwend: "2025-12-17T00:00:00.000Z",
	crwlimit: 600
} );

for ( const page of pages.query['communityrequests-wishes'] ) {
	const { proposer, created, crwtitle } = page;
	if ( proposer.match( /(?:\-WMF|\(WMF\))$/ ) ) {
		console.log( `Skipping staff vote ${ proposer } ${ crwtitle }` );
		continue;
	}
	const entityId = crwtitle.match( /\/(W\d+)/ )[1];
	const baseTitle = `Community Wishlist/${ entityId }`;
	const votesPageResponse = ( await api.get( {
		action: 'query',
		prop: 'revisions',
		titles: `${ baseTitle }/Votes`,
		rvprop: 'content',
		formatversion: 2
	} ) );

	if ( votesPageResponse?.query?.pages?.[0]?.missing ) {
		console.log( `Missing ${ baseTitle }/Votes` );

		// Use the API to create the Votes subpage.
		await api.postWithEditToken( {
			action: 'wishlistvote',
			entity: entityId,
			voteaction: 'add'
		} );
		// Then immediately remove the vote.
		await api.postWithEditToken( {
			action: 'wishlistvote',
			entity: entityId,
			voteaction: 'remove'
		} );
		// Now we can proceed to adding the proposer's vote…
	}

	const votesContent = votesPageResponse?.query?.pages?.[0]?.revisions?.[0]?.content || '';

	const regexp = new RegExp( `\\|username=${ RegExp.escape( proposer ) }\\|` );
	if ( votesContent.match( regexp ) ) {
		console.log( `Already voted: ${ proposer } ${ crwtitle }` );
	} else {
		await api.edit( `${ baseTitle }/Votes`, ( rev ) => {
			return {
				text: `{{#CommunityRequests:vote|username=${ proposer }|comment=|timestamp=${ created }}}\n` + votesContent,
				summary: `Adding support vote on behalf of [[User:${ proposer }|${ proposer }]] ([[phab:T415231]])`
			}
		} );
	}
}

There were a couple of hiccups and bad edits I needed to fix manually, but we should be all set now. If you care to go through the contribs: https://meta.wikimedia.org/w/index.php?title=Special:Contributions/MusikAnimal_(WMF)&target=MusikAnimal+%28WMF%29&dir=prev&offset=20260305024114&limit=498

No QA involved here, unless there's desire to verify each and everyone of those wishes by hand, which I assume there's not :-P

Resolving!